Здравствуйте, Qbit86, Вы писали:
C>>Вот за каким хреном так сделали?
Q>Представь, что у тебя твой TestClass реализует не только интерфейс ITest, но и ICompletelyDifferentInterface: Q>
java: types test.Test.I1 and test.Test.I2 are incompatible;
class test.Test.TestClass inherits unrelated defaults for method() from types test.Test.I1 and test.Test.I2
static class TestClass implements I1, I2 {
@Override
public void method() {
I1.super.method();
}
}
Здравствуйте, Codealot, Вы писали:
C>Если у тебя такое произойдет, к какому интерфейсу приведешь — такой и должен быть вызван.
Основная мотивация, стоящая за default interface methods — чтобы можно было добавлять методы в интерфейс, не ломая компиляцию существующих имплементоров.
Если ты автор библиотеки и добавляешь в интерфейс метод, то раньше у тебя все пользователи, реализующие интерфейс, ломались; и им нужно было определить у себя добавленный метод.
С добавлением default interface methods — нет, больше не ломаются, и явно реализовывать новый API им не требуется.
Представь, что твой класс TestClass реализует интерфейс ITest с методом Method() и интерфейс ICompletelyDifferentInterface без метода Method().
И потом автор второго интерфейса добавляет туда свой независимый метод Method().
Если использовать текущий подход C#, то есть изначально требовать явное приведение к нужному типу, то никакой неоднозначности не возникнет, и компиляция клиента не сломается.
Если по умолчанию разрешать вызов без приведения, то добавление нового метода Method() сломает компиляцию, то есть defeats the whole idea.
Здравствуйте, Qbit86, Вы писали:
Q>Если ты автор библиотеки и добавляешь в интерфейс метод, то раньше у тебя все пользователи, реализующие интерфейс, ломались; и им нужно было определить у себя добавленный метод. Q>С добавлением default interface methods — нет, больше не ломаются, и явно реализовывать новый API им не требуется.
Только в некотором подклассе случаев.
Q>Если по умолчанию разрешать вызов без приведения, то добавление нового метода Method() сломает компиляцию, то есть defeats the whole idea.
С фига ли? В большинстве случаев, таких проблем не будет.
defeats the whole idea — это когда у тебя производный класс не умеет то, что умеет базовый.
Ад пуст, все бесы здесь.
Re[2]: default interface methods. Какой все же бред.
Здравствуйте, vsb, Вы писали:
vsb>В жаве работает. А почему в C# ошибка?
Видимо, потому что опять кто-то допустил детишек-переростков до работы взрослых.
does not contain a definition for 'Method' and no accessible extension method 'Method' accepting a first argument of type 'Program.TestClass' could be found
Я не очень понял, в пользу какого тезиса ты приводишь этот пример.
(Джаву я не знаю, поверю написанному на слово.)
В твоём примере, чтобы был доступен метод method() в конкретном наследнике, пришлось его в этом наследнике реализовать, и вызвать там базовый метод, явно указав из какого предка его взять.
Такое и в C# будет работать, пример топик стартера был другой и не про это.
Здравствуйте, Codealot, Вы писали:
vsb>>В жаве работает. А почему в C# ошибка?
C>Видимо, потому что опять кто-то допустил детишек-переростков до работы взрослых.
Так ведь в приведённом примере на Джаве ещё хуже, там будет ошибка компиляции не на попытке вызова метода method(), а ещё на этапе попытки определить имплементора двух интерфейсов без явной реализации метода method():
java: types test.Test.I1 and test.Test.I2 are incompatible;
class test.Test.TestClass inherits unrelated defaults for method() from types test.Test.I1 and test.Test.I2
Здравствуйте, Codealot, Вы писали:
C>С фига ли? В большинстве случаев, таких проблем не будет.
Проблема будет в каждом таком случае, если в интерфейс добавляется метод, который уже есть в других реализуемых интерфейсах.
C>defeats the whole idea — это когда у тебя производный класс не умеет то, что умеет базовый.
Здравствуйте, Qbit86, Вы писали:
Q>Проблема будет в каждом таком случае, если в интерфейс добавляется метод, который уже есть в других реализуемых интерфейсах.
Была, есть и будет есть. Не будет ее только в одном подклассе случаев — когда ты добавляешь метод, для которого можно написать дефолтную реализацию, которая имеет смысл по условиям задачи.
Q>Так он умеет — если работаешь через абстракцию.
Здравствуйте, Qbit86, Вы писали:
Q>Так ведь в приведённом примере на Джаве ещё хуже, там будет ошибка компиляции не на попытке вызова метода method(), а ещё на этапе попытки определить имплементора двух интерфейсов без явной реализации метода method():
Здравствуйте, Codealot, Вы писали:
Q>>Так ведь в приведённом примере на Джаве ещё хуже, там будет ошибка компиляции не на попытке вызова метода method(), а ещё на этапе попытки определить имплементора двух интерфейсов без явной реализации метода method():
C>А, так ты из любителей маскировать ошибки?
А у тебя и нет ошибки, если ты работаешь через абстракции — через ITest или через <TTest> where TTest : ITest. В C# ты это сделать можешь. В Джава (если верить написанному) — нет, потому что она даже не даст тебе определить имплементора.
C>А, так ты из любителей маскировать ошибки?
А, так ты из виртуалов Kolesiki, что ли? Я тогда не будут тратить на тебя время.
Здравствуйте, Qbit86, Вы писали:
vsb>>работает
Q>Я не очень понял, в пользу какого тезиса ты приводишь этот пример.
В пользу тезиса, что ромбовидное наследование является разрешимой проблемой.
Q>Такое и в C# будет работать, пример топик стартера был другой и не про это.
Здравствуйте, Qbit86, Вы писали:
Q>Представь, что у тебя твой TestClass реализует не только интерфейс ITest, но и ICompletelyDifferentInterface: Q>Метод Method() какого интерфейса по-твоему должен быть вызван в вызове `obj.Method()`?
Ты вот такую дурь пишешь, что не сразу поймёшь, что ты умный
Что значит "представь"?? Тебе УЖЕ ДАЛИ ЗАДАЧУ. Ясную как день. Что тебе ещё в ней непонятно? Есть класс, есть метод из интерфейса. И он ОБЯЗАН работать. По кр. мере по программерской логике. А теперь ты выдумываешь СВОЙ пример и что-то начинаешь доказывать. Зачем? Ответь на оригинальный вопрос!
Здравствуйте, Qbit86, Вы писали:
Q>Основная мотивация, стоящая за default interface methods — чтобы можно было добавлять методы в интерфейс, не ломая компиляцию существующих имплементоров.
Хм... а это что за идиотизм?? С чего бы это изменённый предок "не должен" ломать наследников? ЕЩЁ КАК ДОЛЖЕН!!
Если у тебя класс "ГеомФигура" и ты помимо "Площадь" добавил метод "Периметр", все наследники обязаны реализовать правильный метод "Периметр"!
Собственно, потому интерфейс и является КОНТРАКТОМ, что там нет "отлынивающих классов" — любой из наследников ОБЯЗАН корректно реализовать контракт.
Q>Если ты автор библиотеки и добавляешь в интерфейс метод, то раньше у тебя все пользователи, реализующие интерфейс, ломались; и им нужно было определить у себя добавленный метод.
Это в джамшутской терминологии "ломались". В нормальной: "все наследники получали корректирующую ошибку о недореализованном методе".
Q>С добавлением default interface methods — нет, больше не ломаются, и явно реализовывать новый API им не требуется.
Спасибо, б%%%! Мало нам таймбомб — давайте теперь ещё и эти.
Q>Если по умолчанию разрешать вызов без приведения, то добавление нового метода Method() сломает компиляцию, то есть defeats the whole idea.
Не надо про УСЛОВНЫЕ СЛУЧАИ. Есть процесс разработки. Если у тебя есть библиотека и она обновилась, там всегда есть breaking changes. Хочешь — не обновляйся и ничего дореализовывать не надо. Хочешь свежачок — компильни, проверь ошибки, дореализуй контракт. Всё. Просто как день. Здесь не идёт вопрос об обратной совместимости — это эволюция продукта, где допустимо улучшение, требующее правки чужих исходников.
Здравствуйте, Codealot, Вы писали:
C> obj.Method(); // error C>Вот за каким хреном так сделали?
Я с большим подозрением стал относиться к "фичам" C# после версии 8.0; Такое ощущение, что все адекваты махнули рукой и сказали "А, лепите что хотите, я устал спорить!".
Только так можно объяснить поток ОЧЕВИДНО СПОРНЫХ фич, которые обсуждаются хрен знает кем, одабриваются ещё более странными персонами и выкатываются в релиз без широкого обсуждения на общедоступных сайтах. (почему не LJ? Facebook? StackOverflow? Да потому что ТАМ НЕЛЬЗЯ ЗАТКНУТЬ РОТ ОППОНЕНТАМ! )
Унылые, обидчивые посредственности выкатывают очередной высер их банального мозга (или того хуже — плохо понятые фичи других языков) и начинается шапкозакидательство тех, кто смеет этих посредственностей критиковать. Более того — количество критики вообще никак не влияет на последущее решение — фича просто делается по задумке тупого индуса-придумщика и далее преподносится как очередной "прорыв". Я видел кучи обсуждений на мелкомягком междусобойчике в github и ты никогда не доведёшь свою мысль до их замкнутого разума.
Вот почему Nemerle — наше всё. Всегда можно общедоступно обсудить спорные моменты и более того — даже самому реализовать так, как ты считаешь нужным! Да, бывают в жизни и "две правды" сразу, но никто не имеет права принуждать тебя использовать "чужую" правду. У кого какие задачи — тот пусть себе и пилит "наименее спорный вариант" фичи.
Вот дефолтовые методы в интерфейсах — я думал, это что-то полезное! Оказалось, высер.
Здравствуйте, Baiker, Вы писали:
Q>>Основная мотивация, стоящая за default interface methods — чтобы можно было добавлять методы в интерфейс, не ломая компиляцию существующих имплементоров.
B>Хм... а это что за идиотизм?? С чего бы это изменённый предок "не должен" ломать наследников? ЕЩЁ КАК ДОЛЖЕН!! B>Если у тебя класс "ГеомФигура" и ты помимо "Площадь" добавил метод "Периметр", все наследники обязаны реализовать правильный метод "Периметр"! B>Собственно, потому интерфейс и является КОНТРАКТОМ, что там нет "отлынивающих классов" — любой из наследников ОБЯЗАН корректно реализовать контракт.
Когда у вас будет библиотека хотя бы с сотней тысяч пользователей, попробуйте поломать базовый интерфейс.
А если счёт идёт на миллионы строк кода как в интерфейсах .NET?
Представьте, например, что у вас есть большой проект где каждая команда отвечает за свою часть продукта.
Теперь, вы меняете базовый интерфейс, нужно поменять код во всех частях проекта, а для этого придётся ещё и получить одобрение на изменение от всех команд.
Эта функциональность позволяет решить конкретную проблему с наименьшими проблемами.
Если вам нужно, чтобы можно было вызывать без приведения к интерфейсу, используйте класс.
Здравствуйте, _NN_, Вы писали:
_NN>Представьте, например, что у вас есть большой проект где каждая команда отвечает за свою часть продукта. _NN>Теперь, вы меняете базовый интерфейс, нужно поменять код во всех частях проекта, а для этого придётся ещё и получить одобрение на изменение от всех команд.
Ну да, а так по тихому поменяли контракт. Правда у нас теперь "Периметр()" круга равен "Периметр()" квадрата (возвращаясь к примеру выше), но это мелочи, ведь билд не ломается.
Плюс, теперь можно валить на собесах вопросом "чем отличается интерфейс от класса"
Здравствуйте, Doc, Вы писали:
Doc>Здравствуйте, _NN_, Вы писали:
_NN>>Представьте, например, что у вас есть большой проект где каждая команда отвечает за свою часть продукта. _NN>>Теперь, вы меняете базовый интерфейс, нужно поменять код во всех частях проекта, а для этого придётся ещё и получить одобрение на изменение от всех команд.
Doc>Ну да, а так по тихому поменяли контракт. Правда у нас теперь "Периметр()" круга равен "Периметр()" квадрата (возвращаясь к примеру выше), но это мелочи, ведь билд не ломается.
В каком плане поменяли контракт ?
Вот у нас был код:
public interface IA
{
void F();
}
static class X
{
public UseIAF(IA a)
{
a.F();
}
}
class A : IA
{
public void F() { .. .}
}
Добавляем метод по умолчанию.
Можем использовать G, которого раньше не было.
public interface IA
{
void F();
void G() { }
}
static class X
{
public UseIAF(IA a)
{
a.F();
}
public UseIAG(IA a)
{
a.G();
}
}
В реализации интерфейса ничего не меняется как и ожидалось.
class A : IA
{
public void F() { .. .}
}
Какую проблему вы здесь видите ?
Doc>Плюс, теперь можно валить на собесах вопросом "чем отличается интерфейс от класса"
Интерфейс всё ещё остается интерфейсом.
Наследоваться от двух классов нельзя в отличии от реализации множества интерфейсов.
Здравствуйте, _NN_, Вы писали:
_NN>В каком плане поменяли контракт ?
Добавление метода в интерфейс. Это изменение контракта (даже если оно non breaking).
_NN>Какую проблему вы здесь видите ?
В этом теоретическом примере вижу следующие проблемы
— интерфейс более не абстракция.
— интерфейс знает о реализациях, иначе откуда такая уверенность что дефолтный G() подходит для всех реализаций интерфейса.
— если по какой-то причине G() гарантированно подходит для любой реализации, то он может вполне себе быть extension методом.
— программист реализации может просто не заметить добавления G(), который будет не корректный для его реализации. Но в проекте IA.G() рано или поздно кто-то вызовет.
_NN>Интерфейс всё ещё остается интерфейсом. _NN>Наследоваться от двух классов нельзя в отличии от реализации множества интерфейсов.
Только это не основное свойство интерфейса. Интерфейс в первую очередь это абстракция, а теперь можно затянуть в него реализацию.
Не лучше было бы для таких кейсов ввести что-то типа "abstract base class" который бы и реализовывал такое поведение (по сути ведя себя как интерфейс с дефолтными методами)? А пользователю библиотеки уже выбирать наследоваться от интерфейса или такого класса.
Здравствуйте, Doc, Вы писали:
Doc>- интерфейс более не абстракция. Doc>- интерфейс знает о реализациях, иначе откуда такая уверенность что дефолтный G() подходит для всех реализаций интерфейса. Doc>- если по какой-то причине G() гарантированно подходит для любой реализации, то он может вполне себе быть extension методом. Doc>- программист реализации может просто не заметить добавления G(), который будет не корректный для его реализации. Но в проекте IA.G() рано или поздно кто-то вызовет.
Все эти проблемы присущи и абстрактному классу, на который ниже предлагается заменить такие интерфейсы
Doc>Только это не основное свойство интерфейса. Интерфейс в первую очередь это абстракция, а теперь можно затянуть в него реализацию. Doc>Не лучше было бы для таких кейсов ввести что-то типа "abstract base class" который бы и реализовывал такое поведение (по сути ведя себя как интерфейс с дефолтными методами)?
В C# нет множественного наследования, поэтому примерно всё делают через интерфейсы и не всегда интерфейс можно заменить на абстрактный класс.
Такой вот костыль приделали, чтобы работало и плевать, что это идеологически неправильно и портит идею интерфейсов.
Doc>А пользователю библиотеки уже выбирать наследоваться от интерфейса или такого класса.
Весь прикол в том, что я даю API в виде условного интерфейса IPlugin, через который подтягиваю в приложение сторонние dll.
Если разработчики будут выбирать что реализуют, то мне у себя писать отдельные обработчики для интерфейсов и отдельные для классов?
А дальше я обновляю интерфейс для плагинов и у пользователей лотерея, в которой половина старых плагинов работает, т.к. наследовались от класса, а вторая половина на интерфейсе сидела и поломались?
Здравствуйте, karbofos42, Вы писали:
Doc>>- интерфейс более не абстракция. Doc>>- интерфейс знает о реализациях, иначе откуда такая уверенность что дефолтный G() подходит для всех реализаций интерфейса. Doc>>- если по какой-то причине G() гарантированно подходит для любой реализации, то он может вполне себе быть extension методом. Doc>>- программист реализации может просто не заметить добавления G(), который будет не корректный для его реализации. Но в проекте IA.G() рано или поздно кто-то вызовет. K>Все эти проблемы присущи и абстрактному классу, на который ниже предлагается заменить такие интерфейсы
Абстрактный класс не интерфейс. Он поддерживает какой-то уровень абстракции, но при этом обладает реализацией и состоянием. Поэтому пункты выше в большей части к нему не применимы.
K>В C# нет множественного наследования, поэтому примерно всё делают через интерфейсы и не всегда интерфейс можно заменить на абстрактный класс. K>Такой вот костыль приделали, чтобы работало и плевать, что это идеологически неправильно и портит идею интерфейсов.
— Можно было бы реализовать не трогая интерфейсы через абстрактные классы.
— У фичи достаточно узкая область применения, чтобы говорить что это замена множественного наследования.
— Но раз вы сами говорите что обсуждаемая фича — костыль, то какой смысл дальше обсуждать. Хороший код/фичу костылем не назовут
Здравствуйте, Doc, Вы писали:
Doc>Абстрактный класс не интерфейс. Он поддерживает какой-то уровень абстракции, но при этом обладает реализацией и состоянием. Поэтому пункты выше в большей части к нему не применимы.
Применимы. Уровень абстракции достаточно условный и абстрактный класс в общем случае не обязан чем-то отличаться от интерфейсов.
Есть Stream и унаследованные от него MemoryStream, FileStream и какой-нибудь NetworkStream.
Много вот Stream знает про реализацию всего этого добра?
Метод расширения не является аналогом фичи по той причине, что он для всех реализаций интерфейса/наследников класса будет одинаковый,
а человекам нужно поведение по умолчанию с возможность перегрузки для конкретных реализаций.
Если в абстрактном классе добавить виртуальный метод с реализацией, то его добавление так же никто не заметит и не узнает, что можно теперь что-то перегрузить.
Doc>- Можно было бы реализовать не трогая интерфейсы через абстрактные классы. Doc>- У фичи достаточно узкая область применения, чтобы говорить что это замена множественного наследования.
Было бы множественное наследование — думаю, что и фичу бы не добавляли, т.к. было бы доступно это всё через классы сделать.
По сути эта фича приближает интерфейсы к абстрактным классам и есть смысл это использовать там, где не получается взять классы.
Doc>- Но раз вы сами говорите что обсуждаемая фича — костыль, то какой смысл дальше обсуждать. Хороший код/фичу костылем не назовут
Ну, я просто не согласен, что у фичи есть полная альтернатива в языке.
Какой-нибудь "override new" для меня тоже костыль и я бы в язык это не добавлял, как и реализацию методов в интерфейсах.
Только разработчики языка почему-то со мной не советуются
Здравствуйте, karbofos42, Вы писали:
K>Применимы. Уровень абстракции достаточно условный и абстрактный класс в общем случае не обязан чем-то отличаться от интерфейсов.
Тот факт что у абстрактного класса может быть состояние уже меняет все. Как раз свести абстрактный класс к интерфейсу это не общий а частный случай.
K>Есть Stream и унаследованные от него MemoryStream, FileStream и какой-нибудь NetworkStream. K>Много вот Stream знает про реализацию всего этого добра?
Не должен знать.
K>Метод расширения не является аналогом фичи по той причине, что он для всех реализаций интерфейса/наследников класса будет одинаковый, K>а человекам нужно поведение по умолчанию с возможность перегрузки для конкретных реализаций.
Это от дизайна зависит можно использовать расширение или нет.
K>Было бы множественное наследование — думаю, что и фичу бы не добавляли, т.к. было бы доступно это всё через классы сделать.
Здравствуйте, Doc, Вы писали:
Doc>Тот факт что у абстрактного класса может быть состояние уже меняет все. Как раз свести абстрактный класс к интерфейсу это не общий а частный случай.
А такой интерфейс имеет состояние или нет?
interface IA
{
int Value {get;set;}
}
Doc>Это от дизайна зависит можно использовать расширение или нет.
Что от дизайна зависит? Возможность перегрузки метода расширения для разных типов, подобно виртуальным методам?
Doc>Да не про множественное наследование эта фича.
Да ладно?
Doc>Это про добавление методов без ломки совместимости.
И зачем же тогда эту фичу добавили в интерфейсы, если можно было спокойно вместо интерфейса брать класс и там это всё работало бы без изменений языка?
Здравствуйте, karbofos42, Вы писали:
Doc>>Это про добавление методов без ломки совместимости.
K>И зачем же тогда эту фичу добавили в интерфейсы, если можно было спокойно вместо интерфейса брать класс и там это всё работало бы без изменений языка?
Тут уже был разбор как сделать множественное наследование чтобы не было проблем как с виртуальным наследованием в плюсах.
Можете поискать в раздели Философии.
Попробую найти.
Конкретно здесь .NET не умеет множественное наследование классов.
А множественное наследование интерфейсов умеет.
Почему единственным вариантом добавлять новую функциональность не ломая миллионы строк кода это методы по умолчанию в интерфейсе.
Как уже указали в Java аналогичная проблема и такое же решение.
Здравствуйте, _NN_, Вы писали:
_NN>Почему единственным вариантом добавлять новую функциональность не ломая миллионы строк кода это методы по умолчанию в интерфейсе. _NN>Как уже указали в Java аналогичная проблема и такое же решение.
Ну, вот в реальной жизни это действительно помогает?
Или спустя некоторое время вылезают неоднозначности и в итоге люди что-то городят своё?
Не лучше ли было не язык коверкать, а написать какой-нибудь фреймворк для плагинов, который бы генерировал нужные прокси и вызывал все эти дефолтные методы?
Что вот делать банально с переименованием метода?
Держать в итоге в интерфейсе и старое имя и новое? А если я просто в enum добавлю пару значений, то как быть? А если не добавлю, а изменю старые, т.к. решил теперь поддерживать Flags?
Эта фича же достаточно узкой выглядит и, на мой взгляд, через неё удобно быстро выкатить новые фишки в продакшон, а вот как это потом поддерживать и дальше расширять?
У меня просто не было задач, где это было бы нужно, потому на практике не использовал и тут чисто теоретический взгляд.
Здравствуйте, karbofos42, Вы писали:
K>Здравствуйте, _NN_, Вы писали:
K>Эта фича же достаточно узкой выглядит и, на мой взгляд, через неё удобно быстро выкатить новые фишки в продакшон, а вот как это потом поддерживать и дальше расширять? K>У меня просто не было задач, где это было бы нужно, потому на практике не использовал и тут чисто теоретический взгляд.
Эта фича, особенно в jave, гибрид сишарповых extension methods и с++ специализации.
Т.к. состояние класса в интерфейсе недоступно, реализация дефолтового метода может опираться только на существующий интерфес, что делает ее по возможностям полным аналогом extension method.
Но, конкретные реализации интерфейса, могут уже использовть внутренний стейт и переопределять дефолтовую реализацию более оптимальным образом.
Для c# выглядит оверкиллом. Хотя второй вариант может быть полезен.
interface ICountableEnumerable {
bool next();
int aCount() {
int times = 0;
while(next()) {
times++;
}
return times;
}
}
class List: ICountableEnumerable {
int aCount() { return size; } // более оптимальная реализация
}
interface ICollectionWithFeatures {
IReadonlyCollection asReadonly() {
var copy = ...;// копируем исходный набор данных, что бы call-site не "сломался"return copy;
}
}
class ReadonlyList: ICollectionWithFeatures, IReadonlyCollection {
IReadonlyCollection asReadonly() {
return this; // можно не копировать, т.к. мы не даем методов модификации текущей коллекции
}
}
Здравствуйте, vsb, Вы писали:
vsb>java: types test.Test.I1 and test.Test.I2 are incompatible; vsb> class test.Test.TestClass inherits unrelated defaults for method() from types test.Test.I1 and test.Test.I2
Здравствуйте, Codealot, Вы писали:
C>Была, есть и будет есть. Не будет ее только в одном подклассе случаев — когда ты добавляешь метод, для которого можно написать дефолтную реализацию, которая имеет смысл по условиям задачи.
Я правильно понимаю, что это реинкарнация (второй аккаунт) сами знаете кого?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Baiker, Вы писали:
B>Это в джамшутской терминологии "ломались". В нормальной: "все наследники получали корректирующую ошибку о недореализованном методе".
Увижу еще раз такой стиль общения, уйдешь в баню на долго. Пока не денёк.
Что касается обсуждаемого вопроса, в МС всё сделали правильно. Основная проблема в разработке сложного ПО — объем кода и сложность его поддержки, развития. Такое решение не создаёт лишние сложности, а значит упрощает жизнь. Каждый интерфейс может получить нужную ему дефолтную реализацию без лишних телодвижений со стороны программиста. Нам не придется бегать по классам и добавлять реализации вручную.
Если не устраивает эта фича, можно завести базовый класс с виртуальным методом и переопределять его там где надо.
B>Спасибо, б%%%! Мало нам таймбомб — давайте теперь ещё и эти.
Несёшь форменный бред.
B>Не надо про УСЛОВНЫЕ СЛУЧАИ. Есть процесс разработки. Если у тебя есть библиотека и она обновилась, там всегда есть breaking changes. Хочешь — не обновляйся и ничего дореализовывать не надо. Хочешь свежачок — компильни, проверь ошибки, дореализуй контракт. Всё. Просто как день. Здесь не идёт вопрос об обратной совместимости — это эволюция продукта, где допустимо улучшение, требующее правки чужих исходников.
Случай не условный, а самый что не наесть конкретный. Сам недавно пользовался этой фичей — очень удобно. Просто не стоит путать публичный интерфейс класса и интерфейсы им реализуемые. Это могут быть разные реализации. Ты и в ручную можешь сделать несколько явных реализаций и публичный метод с тем же именем.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Baiker, Вы писали:
B>Я с большим подозрением стал относиться к "фичам" C# после версии 8.0; Такое ощущение, что все адекваты махнули рукой и сказали "А, лепите что хотите, я устал спорить!".
Создавай отдельные темы по отдельным фичам (как эта) — обсудим.
Лично мне не нравятся не фичи, а непоследовательность их появления и эволюционист. Авторы языка явно добавляли фичи по мере собственного созревания.
Судя по твоей критике ты пока что до этих фич не дорос.
B>Только так можно объяснить поток ОЧЕВИДНО СПОРНЫХ фич, которые обсуждаются хрен знает кем, одабриваются ещё более странными персонами и выкатываются в релиз без широкого обсуждения на общедоступных сайтах. (почему не LJ? Facebook? StackOverflow? Да потому что ТАМ НЕЛЬЗЯ ЗАТКНУТЬ РОТ ОППОНЕНТАМ! )
Процесс обсуждения фич на гитхабе довольно открытый. На СтекОверфлоу обсуждать что либо нельзя, там другой формат. Про Facebook вообще молчу. Не профильная говнопомойка забаненная у нас в стране за русофобию.
B>Унылые, обидчивые посредственности
Боюсь они даже о твоем существовании не подозревают.
B>выкатывают очередной высер их банального мозга
Истерика какая то, а толку — 0.
B>(или того хуже — плохо понятые фичи других языков) и начинается шапкозакидательство тех, кто смеет этих посредственностей критиковать. Более того — количество критики вообще никак не влияет на последущее решение — фича просто делается по задумке тупого индуса-придумщика и далее преподносится как очередной "прорыв". Я видел кучи обсуждений на мелкомягком междусобойчике в github и ты никогда не доведёшь свою мысль до их замкнутого разума.
У меня лично много претензий к C#. Но направление развития языка правильное. Все добавленные фичи — полезны. Моя главная претензия в том, что они добавлены эволюционным путём. Люди не проектировали язык как единое целое. По сему иногда встречаются недоработки. Но это явно не тот случай. Дефолтные реализации для интерфейсов сделаны не плохою.
B>Вот почему Nemerle — наше всё. Всегда можно общедоступно обсудить спорные моменты и более того — даже самому реализовать так, как ты считаешь нужным! Да, бывают в жизни и "две правды" сразу, но никто не имеет права принуждать тебя использовать "чужую" правду. У кого какие задачи — тот пусть себе и пилит "наименее спорный вариант" фичи.
В Немерле, к сожалению, такой фичи нет. Добавил бы, а не уставил здесь истерику. Тогда по праву мог бы говорить, что вот правильная реализации.
B>Вот дефолтовые методы в интерфейсах — я думал, это что-то полезное! Оказалось, высер.
Очень даже удобно. Недавно применил в реальном боевом проекте чтобы не писать кучу кода. В интерфейсе (который уже был) свойство выставил в одно значение, а в нужных классах реализации переопределил на обратное. Сэкономил море времени.
В том что эта фича не добавляет публичного метода, а создает явные реализации проблемы не вижу. Работа с членами интерфейса должна вестись через интерфейс. Иначе не нужен интерфейс, а нужно пользоваться наследованием реализаций и виртуальными методами.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, _NN_, Вы писали:
_NN>Какую проблему вы здесь видите ?
в том что оно не упало, и есть проблема и теперь мы по тихому используем a.G() вообще не понимая что там, и даже не зная что нам надо его реализовать
не практике будет так:
* обновили библиотеку, ничего не упало, все работает
* спустя время кто-то из команды в интелисенсе найдет этот новый метод, заиспользует,
* если метод имеет какую то дефолтную реализацию — может даже покажется в начале что отлично работает
* спустя еще время потратят хренову тучу времени чтобы понять что мы вообще этот метод не реализовали, и отрабатывает какая то дефолтная заглушка
если уж очень хочется, как альтернатива, могли позволить указывать дефолтные методы, компиляция бы не падала, но всегда кидало бы NotImplemented exception
Здравствуйте, mogadanez, Вы писали:
M>в том что оно не упало, и есть проблема и теперь мы по тихому используем a.G() вообще не понимая что там, и даже не зная что нам надо его реализовать
Этого не может быть. M>не практике будет так: M>* обновили библиотеку, ничего не упало, все работает M>* спустя время кто-то из команды в интелисенсе найдет этот новый метод, заиспользует,
Не найдёт. Ну, то есть если у вас есть MyClass: IFoo, и выражение с типом MyClass, то интеллисенс ничего не найдёт.
Нет у вашего класса метода G().
А вот если где-то есть выражение типа IFoo, то теперь, после пересборки, интеллисенс будет подсказывать наличие G(). M>* если метод имеет какую то дефолтную реализацию — может даже покажется в начале что отлично работает
И будет отлично работать. M>* спустя еще время потратят хренову тучу времени чтобы понять что мы вообще этот метод не реализовали, и отрабатывает какая то дефолтная заглушка M>если уж очень хочется, как альтернатива, могли позволить указывать дефолтные методы, компиляция бы не падала, но всегда кидало бы NotImplemented exception
Мм. Дефолтные методы интерфейсов очень похожи на extension-методы.
Рассмотрим конкретный простой пример: интерфейс IEnumerable<T>.
Допустим, я — автор System.Collections.Generic. Вот я замечаю, что пользователи часто занимаются подсчётом количества элементов в IEnumerable-выражениях.
Раз это популярный код, давайте опубликуем его в библиотеке:
public static class Enumerable
{
public static int Count<T>(IEnumerable<T> enumerable)
{
var i=0;
foreach(var _ in enumerable) i++;
return i;
}
}
У этого кода проблемы с обнаруживаемостью.
Вот у меня есть переменная IEnumerable<int> ints. Ничего интересного мне интеллисенс про неё не подскажет — потому что функция Count расположена где-то снаружи.
Ах, если бы я предусмотрел этот сценарий и добавил метод Count() в IEnumerable<T> с самого начала....
Ну, что ж теперь.
Вспомним про Extension methods:
public static class Enumerable
{
public static int Count<T>(this IEnumerable<T> enumerable)
{
var i=0;
foreach(var _ in enumerable) i++;
return i;
}
}
Теперь моим пользователям необязательно писать var c = Enumerable.Count(ints); можно написать ints.Count().
Ухты, почти что метод интерфейса!
Обратите внимание — уже сейчас интеллисенс показывает наличие Count(), и пользователи могут впиливать его, несмотря на то, что вместо нормального свойства Count для специфичных реализаций IEnumerable<int> у нас вызывается "какая-то дефолтная заглушка".
И вот тут у нас некоторая проблема. Смотрите — допустим, у нас есть интерфейс IList<int>: IEnumerable<int>, который заодно оборудован и методом int Count(). (Да, я знаю, что на самом деле нет, это иллюстративный пример).
Возникает подвох вот какого свойства: выбор между Enumerable.Count() и IList.Count() делается в соответствии со статическим типом выражения.
Так что если я, к примеру, передал какой-нибудь List<int> в метод, где параметр объявлен как IEnumerable<int>, то в результате апкаста информация о наличии "хорошего" метода Count() будет потеряна, и всегда будет вызываться дефолтная заглушка.
Именно поэтому реальная "дефолтная заглушка" устроена не так, как в моём примере выше.
Как бы нам дать возможность авторам реализаций IEnumerable предоставлять свою, эффективную реализацию Count()?
Возвращаемся на шаг назад. Добавим метод Count() в декларацию IEnumerable<T>.
Увы — это очень плохая идея. Ведь у нас нет доступа к коду всех этих классов — миллионы людей сделали сотни тысяч наследников в самых разных местах.
Так что в один коммит мы такой "рефакторинг" не проведём.
Мы публикуем новую версию нашей библиотеки -> пользователи переезжают на неё, их код перестаёт компилироваться.
Полбеды, если "их код" и есть реализация IEnumerable. Они смогут быстро исправить эти ошибки и их приложение начнёт работать с новой версией нашей библиотеки.
Хуже, когда мы выпустили новую версию библиотеки A, а разработчик проекта X использует библиотеку B, в которой реализован наследник IEnumerable.
В лучшем случае проблема обнаружится при сборке X, в худшем — при эксплуатации. То есть где-то в коде X появился вызов collection.Count(), где collection реализован внутри B, скомпилированном ещё с версией A = 1.0.
В классе B.Collection нет метода Count().
Автор X будет вынужден откатиться на версию 1.0 библиотеки A. Даже если он напишет авторам B, что их библиотека не работает с A версии 2.0, то нет гарантии скорого выхода фикса.
Авторы B наблюдают простую картину — народ не спешит переезжать на версию 2.0. "Значит, не надо", решают авторы B и продолжают ездить на версии 1.0. "Двойка — это какой-то обратно-несовместимый отстой", говорят они. "Люди ей не пользуются — мы не собираемся заниматься апгрейдами ради апгрейдов и переписывать наш код ради забавы. Пусть сначала на 2.0 переедет заметная доля нашей аудитории".
Примерно то же самое произойдёт, если мы к методу Count() добавим дефолтную реализацию, кидающую NotImplementedException(). Это ровно второй (худший) вариант сценария, который я привёл выше: проблема, отложенная до рантайма. Пользователю всё равно, падает оно с method not found или с not implemented.
А вот то, как реализованы дефолтные методы в реальном C#, как раз позволяет всё это решить легко и изящно:
1. Даём заглушку всем тем, кто не позаботился о более качественной реализации — также, как для extension method.
2. В отличие от extension method, биндинг вызова для интерфейсного метода происходит по рантайм типу выражения.
То есть если у меня есть класс-реализация IEnumerable<T> со своим быстрым Count(), то я могу быть уверен, что даже при апкасте к IEnumerable вызовется именно она.
Безо всяких даункастов, как в реальном Enumerable.Count().
Самое главное тут — учитывать при анализе наличие трёх групп "пользователей":
1. Те, кто пишут интерфейс
2. Те, кто пишут классы, реализующие интерфейс
3. Те, кто пользуются кодом первых двух, причём как явно, так и неявно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Этого не может быть.
Почему? "Anything that can go wrong will go wrong."
S>Не найдёт. Ну, то есть если у вас есть MyClass: IFoo, и выражение с типом MyClass, то интеллисенс ничего не найдёт. S>Нет у вашего класса метода G(). S>А вот если где-то есть выражение типа IFoo, то теперь, после пересборки, интеллисенс будет подсказывать наличие G().
в примере выше — как раз через интерфейс — c чего бы его не найти?
public UseIAG(IA a)
{
a.G();
}
M>>* если метод имеет какую то дефолтную реализацию — может даже покажется в начале что отлично работает S>И будет отлично работать.
в каком то количестве случаев, а в каких то не будет. что в примерах выше должен возвращать .perimeter()? 0?
S> Примерно то же самое произойдёт, если мы к методу Count() добавим дефолтную реализацию, кидающую NotImplementedException(). Это ровно второй (худший) вариант сценария, который я привёл выше: проблема, отложенная до рантайма. Пользователю всё равно, падает оно с method not found или с not implemented.
согласен — поэтому по мне проще/правильнее требовать реализовать
Здравствуйте, mogadanez, Вы писали:
S>>А вот если где-то есть выражение типа IFoo, то теперь, после пересборки, интеллисенс будет подсказывать наличие G(). M>в примере выше — как раз через интерфейс — c чего бы его не найти?
M> public UseIAG(IA a)
M> {
M> a.G();
M> }
M>>>* если метод имеет какую то дефолтную реализацию — может даже покажется в начале что отлично работает S>>И будет отлично работать. M>в каком то количестве случаев, а в каких то не будет. что в примерах выше должен возвращать .perimeter()? 0?
Понятное дело, что не для всего можно найти удовлетворительную реализацию. Где-то можно возвращать 0 (типа "не определено") или даже бросать исключение. Но и не каждый интерфейс стоит таким образом, через дефолтовый метод расширять: что же удивляться, если штуковина кем-то не правильно может быть использована и создавать проблемы от этого? Причина не в фиче, а в использовании не по делу, как и во всём
Фича позволяет расширить, например, IDbCommand Interface, добавив на уровне интерфейса возможность осуществлять асинхронные вызовы. По дефолту может делать тупо переадресовывая к синхронным, но хорошая реализация сможет изменить это поведение и сделать как надо. По сути, это не добавляет какой-то новый функционал к интерфейсу, а лишь расширяет возможности использования имеющегося самым удобным для всех (и разработчикам интерфейса, а тем, кто его реализует и тем, кто им пользуется) образом.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, mogadanez, Вы писали:
M>в примере выше — как раз через интерфейс — c чего бы его не найти?
M>
M> public UseIAG(IA a)
M> {
M> a.G();
M> }
M>
Я вроде полностью описал ситуацию. Если кто-то получает ссылку на IA, то им можно безопасно пользоваться.
В том числе, полагаться и на то, что G() сделает что-то осмысленное.
M>>>* если метод имеет какую то дефолтную реализацию — может даже покажется в начале что отлично работает S>>И будет отлично работать.
M>в каком то количестве случаев, а в каких то не будет. что в примерах выше должен возвращать .perimeter()? 0?
У .perimeter не может быть разумной дефолтной реализации. Это нормально. Дефолтные методы в интерфейсах не обязывались быть применимыми в 100% сценариев. Ведь никто не заставляет вас ими пользоваться.
Дефолтные методы применимы только в том случае, если контракт метода можно реализовать в терминах других методов.
Вот вам ещё пример
public interface ICharReader
{
char? Read();
int Read(char[] destination, int count)
{
var readCount = 0;
char? r;
while((r = Read()).HasValue && readCount<count)
destination[readCount++] = r.Value;
return readCount;
}
}
Интерфейс выставляет своим потребителям два метода. Там, где вы пишете свой UseICharReader(ICharReader reader), вы вольны использовать любой из них.
А вот когда вы реализуете этот интерфейс, второй метод вы реализовывать не обязаны. Какой смысл заставлять вас в вашем классе копировать ту реализацию, которую я здесь привёл?
Вот если ваш ридер способен реализовать тот же контракт более оптимально, вы можете сделать свою перегрузку.
Это не значит, что теперь надо втыкать дефолтные методы вообще везде. Вот, например, такое определение интерфейса ICharReader особого смысла не имеет:
public interface ICharReader
{
char? Read() => null;
int Read(char[] destination, int count)
{
var readCount = 0;
char? r;
while((r = Read()).HasValue && readCount<count)
destination[readCount++] = r.Value;
return readCount;
}
}
Вроде бы "ничего плохого", но фактически это введение в заблуждение того, кто будет интерфейс реализовывать. Ведь компилятор допустит "реализацию", в которой своего кода вообще нет.
Впрочем, это не означает, что такой паттерн в принципе неприменим. Есть довольно много т.н. "маркерных" интерфейсов, которые не содержат никаких методов.
Ничему не противоречит и маркерный интерфейс, в котором есть набор методов по умолчанию. Ну, типа пометили вы свой класс как :IExoticSerializable — и всё, можно его сериализовывать в моём экзотическом фреймворке. Просто дефолтная сериализация будет медленной, через рефлексию. А если вас это не устраивает, вы просто предоставите свой метод ExoticSerialize(IExoticSerializationContext), который всё будет делать безо всякой рефлексии.
Но это я отвлёкся. Можно вообразить и другие неожиданные способы употребления дефолтных методов — например, вот такую:
public interface ICharReader
{
char? Read()
{
var chars = new char[1];
return (Read(chars, 1) == 1) ? chars[0] : null;
}
int Read(char[] destination, int count)
{
var readCount = 0;
char? r;
while((r = Read()).HasValue && readCount<count)
destination[readCount++] = r.Value;
return readCount;
}
}
Девиантное сознание даже способно усмотреть здесь какой-то смысл: тут достаточно реализовать любой из методов. Второй метод получится из первого очевидным способом.
Но, естественно, делать так не надо — ведь компилятор с успехом прожуёт и "реализацию" типа class MyReader: ICharReader{}, которая будет переполнять стек при использовании.
На всякий случай повторюсь: это всего лишь означает, что дефолтные методы не являются универсальным средством на все случаи жизни. Они спроектированы для более-менее конкретной задачи, и для неё они подходят идеально — лучше, чем что бы то ни было другое. Не стоит ожидать от них никаких особенных чудес, вроде "научиться вычислять периметр для фигуры заранее неизвестной формы" или "обязать реализацию перегрузить один из N методов".
M>согласен — поэтому по мне проще/правильнее требовать реализовать
Повторюсь: не все находятся в таких условиях, когда есть возможность чего-то требовать.
Если у вас такая возможность есть — пожалуйста, добавляйте в интерфейс обычный метод. Эта возможность была в языке с версии 1.0, и никуда не делась.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Как бы нам дать возможность авторам реализаций IEnumerable предоставлять свою, эффективную реализацию Count()?
Никак. Этот интерфейс для этого не предназначен.
Нужно устранять причину, по которой у нас массово вдруг коллекции кастуются к IEnumerable.
Ну, может Linq переделать, чтобы ICollection на каждый чих не "понижал" до IEnumerable.
S>Возвращаемся на шаг назад. Добавим метод Count() в декларацию IEnumerable<T>.
И нарушим суть этого интерфейса, который вообще может предоставлять бесконечные данные или подсчёт их количества может занять неделю.
Зато теперь пользователи будут думать, что раз есть такой метод, значит он оптимально написан и можно вызывать.
S>Мы публикуем новую версию нашей библиотеки -> пользователи переезжают на неё, их код перестаёт компилироваться.
Если это пользователи, которые делают свои коллекции, реализующие интерфейсы, то им лучше знать, что теперь появился такой метод и им лучше в своих коллекциях его нормально реализовать.
Если это пользователи, которые используют классы, реализующие эти интерфейсы, то у них всё прекрасно будет компилироваться.
S>Полбеды, если "их код" и есть реализация IEnumerable. Они смогут быстро исправить эти ошибки и их приложение начнёт работать с новой версией нашей библиотеки.
Это нормально и правильно, а не так, что втихаря подсовывать новые фичи, а потом я будут разбираться с issue, что мой метод уходит в бесконечный цикл, т.к. кто-то там Count оказывается реализовал.
S>Хуже, когда мы выпустили новую версию библиотеки A, а разработчик проекта X использует библиотеку B, в которой реализован наследник IEnumerable. S>В лучшем случае проблема обнаружится при сборке X, в худшем — при эксплуатации. То есть где-то в коде X появился вызов collection.Count(), где collection реализован внутри B, скомпилированном ещё с версией A = 1.0.
А ещё мы могли не менять интерфейс, но поменять поведение методов. Так себе идея — использовать разные версии библиотек в одном проекте.
S>Авторы B наблюдают простую картину — народ не спешит переезжать на версию 2.0. "Значит, не надо", решают авторы B и продолжают ездить на версии 1.0. "Двойка — это какой-то обратно-несовместимый отстой", говорят они. "Люди ей не пользуются — мы не собираемся заниматься апгрейдами ради апгрейдов и переписывать наш код ради забавы. Пусть сначала на 2.0 переедет заметная доля нашей аудитории".
Обычно наоборот: авторы забивают на старые версии, т.к. тут Span завезли или что-то ещё и нужно скорее новое прикручивать, а пользователи с .NET FW 4.5 могут сидеть с древними версиями, т.к. на них всем плевать.
S>1. Даём заглушку всем тем, кто не позаботился о более качественной реализации — также, как для extension method.
Эту заглушку можно дать в виде статического хелпера. Кто не хочет утруждаться реализацией или его устраивает "стандартное" поведение, то не развалится написать 3 строчки кода, вызывающие этот хелпер.
Зато он точно обратит внимание на появление этого метода и осознанно это сделает, а не прохлопает момент, т.к. не заметил это.
S>2. В отличие от extension method, биндинг вызова для интерфейсного метода происходит по рантайм типу выражения. S>То есть если у меня есть класс-реализация IEnumerable<T> со своим быстрым Count(), то я могу быть уверен, что даже при апкасте к IEnumerable вызовется именно она. S>Безо всяких даункастов, как в реальном Enumerable.Count().
Но есть нюанс. Если я у себя реализовал метод "long Count()", а в интерфейс потом добавили "int Count()".
Здравствуйте, karbofos42, Вы писали:
K>Никак. Этот интерфейс для этого не предназначен.
Хм. А зачем тогда потребовался Enumerable.Count()?
K>Нужно устранять причину, по которой у нас массово вдруг коллекции кастуются к IEnumerable. K>Ну, может Linq переделать, чтобы ICollection на каждый чих не "понижал" до IEnumerable.
Linq тут ни при чём. Есть 100500 мест, в которых ICollection/IList/ISet/IDictionary может быть "понижен" до IEnumerable.
И в очень многих случаях речь идёт не об одном выражении, а нескольких совершенно разных местах, так что простое "спрямление" вызова сделать не удастся.
K>И нарушим суть этого интерфейса, который вообще может предоставлять бесконечные данные или подсчёт их количества может занять неделю. K>Зато теперь пользователи будут думать, что раз есть такой метод, значит он оптимально написан и можно вызывать.
Зачем гадать? Мы смотрим на вполне существующий фреймворк, где .Count() можно вызвать на любом IEnumerable.
Где массовая катастрофа? Где все эти миллионы жертв сценария "Я вызвал .Count() на бесконечном IEnumerable и приложение взорвалось"?
Оказывается, что даже заведомо неудачный (в отличие от примера с ICharReader) пример с IEnumerable и Count() ни к какой катастрофе не приводит.
То есть мы обсуждаем воображаемую опасность против реальных неудобств.
K>Если это пользователи, которые используют классы, реализующие эти интерфейсы, то у них всё прекрасно будет компилироваться.
И? И что будет происходить при эксплуатации этого прекрасно скомпилированного кода?
K>Это нормально и правильно, а не так, что втихаря подсовывать новые фичи, а потом я будут разбираться с issue, что мой метод уходит в бесконечный цикл, т.к. кто-то там Count оказывается реализовал.
K>А ещё мы могли не менять интерфейс, но поменять поведение методов. Так себе идея — использовать разные версии библиотек в одном проекте.
Ну почему же. Используется одна версия библиотек.
K>Обычно наоборот: авторы забивают на старые версии, т.к. тут Span завезли или что-то ещё и нужно скорее новое прикручивать, а пользователи с .NET FW 4.5 могут сидеть с древними версиями, т.к. на них всем плевать.
Это вам так кажется, потому что разработчики дотнета очень сильно вкладываются в backwards compatibility. И именно по этой причине.
Одно дело — опубликовать обратно-совместимую версию с новыми фичами. Тогда вся целевая аудитория быстро переезжает на неё, и авторы всех этих сторонних библиотек быстренько бегут к тому же светлому будущему, что и все.
А другое дело — выкатить версию, которая сломает 90% пользовательского кода.
Обратите внимание — часть народу сидит на FW 4.5, часть — на Net 6.0. Но никто не сидит на FW 2.0 или Net.Core 3.0. Как вы думаете, почему?
K>Эту заглушку можно дать в виде статического хелпера.
Этот способ уже есть. См. Enumerable.Count().
K>Кто не хочет утруждаться реализацией или его устраивает "стандартное" поведение, то не развалится написать 3 строчки кода, вызывающие этот хелпер. K>Зато он точно обратит внимание на появление этого метода и осознанно это сделает, а не прохлопает момент, т.к. не заметил это.
K>Но есть нюанс. Если я у себя реализовал метод "long Count()", а в интерфейс потом добавили "int Count()".
То рано или поздно вам придётся это починить, т.к. по соглашению все Count/Count() возвращают int. А ваш метод должен называться LongCount()
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Хм. А зачем тогда потребовался Enumerable.Count()?
Затем, что ради универсальности Linq всё сводит к IEnumerable, а не ICollection.
Я примерно ни разу за пределами Linq не вызывал Count(), т.к. это слишком дорогая операция.
Да и в Linq единичные случаи использования, т.к. метод потенциально приводит к неоднозначному результату.
Чаще идёт:
var items = expression.ToList();
if (items.Count < 2)
{
...
}
S>Linq тут ни при чём. Есть 100500 мест, в которых ICollection/IList/ISet/IDictionary может быть "понижен" до IEnumerable.
Вот как-то ни разу не встречал таких мест. Обычно всё же до IEnumerable не приходится опускаться и останавливается всё на ICollection/IReadOnlyCollection.
S>И в очень многих случаях речь идёт не об одном выражении, а нескольких совершенно разных местах, так что простое "спрямление" вызова сделать не удастся.
Ну, поэтому в Linq и используют IEnumerable.
Отдельные методы можно было продублировать для ICollection, IList и т.п. но похоже решили, что в итоге это не даст профита и проще уже везде IEnumerable использовать и внутри проверки напихать
S>Зачем гадать? Мы смотрим на вполне существующий фреймворк, где .Count() можно вызвать на любом IEnumerable. S>Где массовая катастрофа? Где все эти миллионы жертв сценария "Я вызвал .Count() на бесконечном IEnumerable и приложение взорвалось"?
Это метод расширения, который не является частью интерфейса.
Если бы в IEnumerable было свойство Count, то появились бы недовольные, т.к. непонятно где это свойство реализовано, где исключение выдаст, где ещё что произойдёт.
В C# вот массивы якобы реализуют IList, при этом на часть методов кидают исключения — не встречал ещё людей, которым это было нормально.
Вот и бесконечным IEnumerable придётся кидать исключения на Count().
Да и даже Count в виде метода-расширения приводит к тому, что появляется подобный код:
if (items.Count() > 0)
{
foreach (var item in items)
{
...
}
}
else
{
...
}
Хотя можно было хотя бы уж вызвать Any().
Хорошо если там items — List, а то может быть и какой-нибудь вычисляемой коллекцией, повторный проход которой выдаст другой результат и может получиться увлекательная отладка.
S>Оказывается, что даже заведомо неудачный (в отличие от примера с ICharReader) пример с IEnumerable и Count() ни к какой катастрофе не приводит. S>То есть мы обсуждаем воображаемую опасность против реальных неудобств.
Оба примера сами по себе воображаемые.
S>И? И что будет происходить при эксплуатации этого прекрасно скомпилированного кода?
Он будет прекрасно работать, т.к. новые методы интерфейсов в нём как не вызывались, так и не вызываются и совершенно безразлично сколько их там добавилось и что они делают.
S>Ну почему же. Используется одна версия библиотек.
В рантайме будет подключена одна версия. Только одна часть кода будет с ней как со старой версией, другая часть — с новой, где методы добавились.
Будет весело, когда устаревшие методы будут удалены из новой версии.
S>Обратите внимание — часть народу сидит на FW 4.5, часть — на Net 6.0. Но никто не сидит на FW 2.0 или Net.Core 3.0. Как вы думаете, почему?
Ну, у меня в требованиях от заказчика поддержка Win 7 + FW 4.5 у клиентов.
С радостью бы перешёл на 4.6.1, чтобы были доступны библиотеки под .NET Standard 2.0 и технически тут нет никаких проблем, но есть бюрократия.
Технически у меня нет проблем перейти хоть на .NET FW 4.8.
Вот на .NET Core уже проблема.
S>Этот способ уже есть. См. Enumerable.Count().
Тогда никто не сможет перегрузить вызов свой оптимальной версией, ведь именно для этого понадобилось коверкать интерфейсы.
S>То рано или поздно вам придётся это починить, т.к. по соглашению все Count/Count() возвращают int. А ваш метод должен называться LongCount()
Ну, главное — чтобы для всего соглашения были и когда-нибудь не решили их поменять
Здравствуйте, Codealot, Вы писали:
C>Ровно наоборот. Как они сделали — нужно делать каст вручную всегда, даже когда никаких конфликтов нет.
Попробуй мыслить в терминах абстракции. Пойми, что раз ты завёл интерфейсы, то работать с классами иже не имеешь права.
Это как раз джамшуты, о которых ты тут смел заикнуться, нахреначивают интерфейсов, а потом говнокодят используя не их, а классы их реализующими, напрямую. Ввел интерфейс — забудь про работу с классами. Прямо сделай себе фабричный метод или используй IoC для ассоциации реализации с интерфейсом и забить о наличии конкретного класса, так как ты работаешь с абстракцией.
Тогда то, что данная фича не рассчитана на добавление реализации в публичный интерфейс классов тебя нисколько не удивит. Если тебе нужен публичны интерфейс класса, не реализуй его через интерфейсы, а пользуйся наследованием реализации.
Я зачастую интерфейсы специально реализую явно, а классы с реализацией делаю internal или private чтобы ни у кого (даже мало квалифицированного) не возникало желание использовать их в обход интерфейсов.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Qbit86, Вы писали:
vsb>>>В жаве работает. А почему в C# ошибка? Q>Так ведь в приведённом примере на Джаве ещё хуже, там будет ошибка компиляции не на попытке вызова метода method(), а ещё на этапе попытки определить имплементора двух интерфейсов без явной реализации метода method():
Может в c# сделали и более идеологически верно, но в java более практически полезно. На практике такой конфликт дефолтных методов явно будет жуткой редкостью, лечится элементарно, зато возможность писать new MyThing().defaultMethod() или использовать var намного полезнее и требуется гораздо чаще.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, VladD2, Вы писали:
VD>Попробуй мыслить в терминах абстракции. Пойми, что раз ты завёл интерфейсы, то работать с классами иже не имеешь права.
Это у тебя тяжелый случай синдрома "чем больше абстракций, тем лучше".
VD>Тогда то, что данная фича не рассчитана на добавление реализации в публичный интерфейс классов тебя нисколько не удивит. Если тебе нужен публичны интерфейс класса, не реализуй его через интерфейсы, а пользуйся наследованием реализации.
В C# нет множественного наследования реализаций.
VD>Я зачастую интерфейсы специально реализую явно, а классы с реализацией делаю internal или private чтобы ни у кого (даже мало квалифицированного) не возникало желание использовать их в обход интерфейсов.
Да, ни у кого. В том числе у тех, кто намного опытнее и талантливее тебя.
Если кратко, оглядываясь на причину появления аналогичного функционала в Java, то для обратной совместимости.
Данная "фича" позволяет разработчику добавлять новые методы в интерфейсы, не ломая существующие имплементации этих интерфейсов.
Здравствуйте, Doc, Вы писали:
Doc>Да не про множественное наследование эта фича.
Не про наследование, но про подключение реализации. См. п.3 по твоей же ссылке:
As it turns out, adding default interface implementations provides the elements of the "traits" language feature (https://en.wikipedia.org/wiki/Trait_(computer_programming)). Traits have proven to be a powerful programming technique.
Трейты, миксины — это как раз та самая борьба с врожденными уродствами ООП, попытка получить наследование реализации без наследования контракта. Интерфейсы+трейты покрывают весь функционал обычного наследования, оставаясь при этом в рамках привычного ОО-полиморфизма, при этом гибче и менее проблемно.
Здравствуйте, Sinclair, Вы писали:
S>Ничему не противоречит и маркерный интерфейс, в котором есть набор методов по умолчанию. Ну, типа пометили вы свой класс как :IExoticSerializable — и всё, можно его сериализовывать в моём экзотическом фреймворке. Просто дефолтная сериализация будет медленной, через рефлексию. А если вас это не устраивает, вы просто предоставите свой метод ExoticSerialize(IExoticSerializationContext), который всё будет делать безо всякой рефлексии.
В таком раскладе оно ничем не лучше опциональной реализации IExoticSerializable.
Юзкейс этой фичи несколько иной. Это скорее замена базовых абстрактных классов типа того же Stream. С одной стороны мы не навешиваем обузу в виде прибитого гвоздями базового класса, а с другой не обременяем реализатора кучей работы по реализации дефолного поведения.
Вобщем, в википедии неплохо описано тут.
Traits combine aspects of protocols (interfaces) and mixins. Like an interface, a trait defines one or more method signatures, of which implementing classes must provide implementations. Like a mixin, a trait provides additional behavior for the implementing class.
public static int Count<TSource>(this IEnumerable<TSource> source) {
if (source == null) throw Error.ArgumentNull("source");
ICollection<TSource> collectionoft = source as ICollection<TSource>;
if (collectionoft != null) return collectionoft.Count;
ICollection collection = source as ICollection;
if (collection != null) return collection.Count;int count = 0;
using (IEnumerator<TSource> e = source.GetEnumerator()) {
checked {
while (e.MoveNext()) count++;
}
}
return count;
}
Вот чтобы убрать выделенную срань, а так же дать возможность выйти за рамки ICollection/ICollection<T> не трогая BCL — и нужны default methods/
S>А другое дело — выкатить версию, которая сломает 90% пользовательского кода. S>Обратите внимание — часть народу сидит на FW 4.5, часть — на Net 6.0. Но никто не сидит на FW 2.0 или Net.Core 3.0.
Это ты слишком оптимистичен. Мы вот уже год пытаемся с 3.1 слезть, но пока только отдельные сервисы переползли. Геморой начался с апгрейда SDK, но его со второго подхода удалось победить (на первом подходе что то сломалось у билд-агентов, а у девопсов, как обычно, есть более срочные задачи чтобы с этим разбираться). Потом ждали когда сборку базовых образов в порядок приведу, чтобы легко можно было добавить версию с 6.0. Потом что то в ASP.NET отломалось (без breaking changes не обошлось). Вобщем, уже 7.0 зарелизен, а мы все еще на 6.0 переползаем с 3.1.
С FW да, попроще было. Но и там МС подкладывал свинью в виде совместимости со старыми версиями ОС. И продолжает подкладывать — 4.8.1 требует WIn11/WinServer2019. Последнее может быть большой проблемой, ибо работает — не трожь.
Здравствуйте, Codealot, Вы писали:
VD>>Я зачастую интерфейсы специально реализую явно, а классы с реализацией делаю internal или private чтобы ни у кого (даже мало квалифицированного) не возникало желание использовать их в обход интерфейсов. C>Да, ни у кого. В том числе у тех, кто намного опытнее и талантливее тебя.
Это прям свежий звук в проектировании софта, дизайн для тех кто опытнее и талантливее.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Тут интересно посмотреть на его реализацию:
Да, именно поэтому я привёл ссылку на неё.
Там проблема даже не в том, чтобы убрать эту "срань", а в том, что нет способа добавить в эту срань даункаст к чему-то ещё, что позволит обойтись без итерирования.
Например, ImmutableList реализует IReadOnlyCollection, а не ICollection — и всё, приплызд.
НС>Это ты слишком оптимистичен. Мы вот уже год пытаемся с 3.1 слезть, но пока только отдельные сервисы переползли. Геморой начался с апгрейда SDK, но его со второго подхода удалось победить (на первом подходе что то сломалось у билд-агентов, а у девопсов, как обычно, есть более срочные задачи чтобы с этим разбираться). Потом ждали когда сборку базовых образов в порядок приведу, чтобы легко можно было добавить версию с 6.0. Потом что то в ASP.NET отломалось (без breaking changes не обошлось). Вобщем, уже 7.0 зарелизен, а мы все еще на 6.0 переползаем с 3.1. НС>С FW да, попроще было. Но и там МС подкладывал свинью в виде совместимости со старыми версиями ОС. И продолжает подкладывать — 4.8.1 требует WIn11/WinServer2019. Последнее может быть большой проблемой, ибо работает — не трожь.
Ок, согласен, погорячился.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Codealot, Вы писали:
НС>>Это прям свежий звук в проектировании софта, дизайн для тех кто опытнее и талантливее. C>Особый дизайн не нужен, просто не мешай.
Это так не работает. Если что то можно сделать неправильно, оно обязательно будет сделано неправильно (если, конечно, твоим кодом пользуются другие люди). После чего ты будешь расхлебывать свою ориентацию на более опытных и талантливых по полной.
Что особенно забавно, даже если ты пишешь код исключительно для себя (а кто может быть опытнее и талантливее себя любимого?), то если вдруг такое случится, что ты вернешься к этому коду через годик-другой, то внезапно окажется что ты не тот опытный и талантливый, на которого был рассчитан тот дизайн. И, в лучшем случае, это даст тебе возможность стать чуть опытнее. А в худшем через год-два oops!, I did it again.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Статический хелпер не обеспечимвает полиморфизма.
Он и не должен это делать
Кто не хочет писать свою оптимальную реализацию или его устраивает некая стандартная, в реализации метода просто вызывает этот хелпер.
Здравствуйте, Codealot, Вы писали:
C>Это у тебя тяжелый случай синдрома "чем больше абстракций, тем лучше".
У меня синдромов нет. Если абстракции на уровне типов не нужны я и функциями могу прекрасно обойтись. И интерфейсы использовать не буду, если они не нужны.
А вот у тебя явные проблемы в вопросе абстракций есть, что и послужило причиной к этому флэйму.
C>В C# нет множественного наследования реализаций.
Да. И дефолтные члены интерфейсов этого не изменяют.
C>Да, ни у кого. В том числе у тех, кто намного опытнее и талантливее тебя.
И у таких тоже. Но это, явно, не твой случай.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Codealot, Вы писали:
C>В C# нет множественного наследования реализаций.
Не совсем. В Delphi для ком интерфейса есть директива Implements. То есть за реализацию интерфейса отвечало поле объекта.
С приходом SG можно генерить не только реализацию интерфейса, но и методы и свойства класса типа поля.
По сути это и будет аналогом множественного наследования C++
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, karbofos42, Вы писали:
K>Кто не хочет писать свою оптимальную реализацию или его устраивает некая стандартная, в реализации метода просто вызывает этот хелпер.
Я тут рядом привел исходный код Enumerable.Count(). Сможешь его переписать со статическим хелпером?
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Это так не работает. Если что то можно сделать неправильно, оно обязательно будет сделано неправильно (если, конечно, твоим кодом пользуются другие люди). После чего ты будешь расхлебывать свою ориентацию на более опытных и талантливых по полной.
Я уже понял, что твоя основная задача — прикрыть себе задницу
НС>А в худшем через год-два oops!, I did it again.
Здравствуйте, VladD2, Вы писали:
VD>У меня синдромов нет. Если абстракции на уровне типов не нужны я и функциями могу прекрасно обойтись. И интерфейсы использовать не буду, если они не нужны.
Сам написал "раз ты завёл интерфейсы, то работать с классами иже не имеешь права", никто за язык не тянул.
Путаешься в показаниях
VD>А вот у тебя явные проблемы в вопросе абстракций есть, что и послужило причиной к этому флэйму.
Ага-ага. Кто-то решил пихать в интерфейсы функции, которым там не место, и почему-то ему взбрело в голову, что это и есть основной вариант использования интерфейсов. А проблемы почему-то у меня
VD>И у таких тоже.
Да, и и таким тоже ты будешь ставить палки в колеса.
Ад пуст, все бесы здесь.
Re[3]: default interface methods. Какой все же бред.
Здравствуйте, VladD2, Вы писали:
VD>Судя по твоей критике ты пока что до этих фич не дорос.
Тут есть два варианта — либо не догнал, либо перегнал. Все будет нравиться только в том случае, если ты находишь ровно на том же уровне.
И да, к тебе это тоже относится.
static void Main (string[]args)
{
var obj = new TestClass();
obj.Method(); // error
}
interface ITest
{
void Method();
}
class TestClass:ITest
{
void ITest.Method() {}
}
Есть пять областей видимости методов: public, protected, internal, private и реализации интерфейсов.
Вызывать у объекта мы можем только "видимые" методы. В вашем примере публичные.
Есть соглашение, что публичный метод с совпадающей сигнатурой может использоваться как метод реализации интерфейса.
Но почему решили, что должно быть верно обратное?