Re[9]: Breaking change
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.12.22 13:17
Оценка: +1
Здравствуйте, 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. Те, кто пользуются кодом первых двух, причём как явно, так и неявно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[10]: Breaking change
От: mogadanez Чехия  
Дата: 15.12.22 14:02
Оценка:
Здравствуйте, 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.


согласен — поэтому по мне проще/правильнее требовать реализовать
Re[11]: Breaking change
От: _FRED_ Черногория
Дата: 15.12.22 14:37
Оценка: 85 (2) +3
Здравствуйте, 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.
Re[11]: Breaking change
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.12.22 16:23
Оценка: +1
Здравствуйте, 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, и никуда не делась.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: Diamond inheritance
От: VladD2 Российская Империя www.nemerle.org
Дата: 15.12.22 17:30
Оценка:
Здравствуйте, Codealot, Вы писали:

C>Ты не в курсе, как делается explicit implementation при конфликтах?


Вот люди и сделали, чтобы не было конфликта и не нужно было вручную в 100500 местах бессмысленный код писать. А где надо — всегда можно написать.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[10]: Breaking change
От: karbofos42 Россия  
Дата: 15.12.22 19:25
Оценка: :)
Здравствуйте, 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()".
Re[11]: Breaking change
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.12.22 19:53
Оценка:
Здравствуйте, 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()
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: Breaking change
От: karbofos42 Россия  
Дата: 15.12.22 20:44
Оценка:
Здравствуйте, 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()


Ну, главное — чтобы для всего соглашения были и когда-нибудь не решили их поменять
Re[6]: Diamond inheritance
От: Codealot Земля  
Дата: 15.12.22 21:07
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Вот люди и сделали


Обычно оно делается только при возникновении конфликтов.

VD>чтобы не было конфликта и не нужно было вручную в 100500 местах бессмысленный код писать.


Ровно наоборот. Как они сделали — нужно делать каст вручную всегда, даже когда никаких конфликтов нет.
Ад пуст, все бесы здесь.
Re[7]: Diamond inheritance
От: VladD2 Российская Империя www.nemerle.org
Дата: 15.12.22 22:38
Оценка: +2
Здравствуйте, Codealot, Вы писали:

C>Ровно наоборот. Как они сделали — нужно делать каст вручную всегда, даже когда никаких конфликтов нет.


Попробуй мыслить в терминах абстракции. Пойми, что раз ты завёл интерфейсы, то работать с классами иже не имеешь права.

Это как раз джамшуты, о которых ты тут смел заикнуться, нахреначивают интерфейсов, а потом говнокодят используя не их, а классы их реализующими, напрямую. Ввел интерфейс — забудь про работу с классами. Прямо сделай себе фабричный метод или используй IoC для ассоциации реализации с интерфейсом и забить о наличии конкретного класса, так как ты работаешь с абстракцией.

Тогда то, что данная фича не рассчитана на добавление реализации в публичный интерфейс классов тебя нисколько не удивит. Если тебе нужен публичны интерфейс класса, не реализуй его через интерфейсы, а пользуйся наследованием реализации.

Я зачастую интерфейсы специально реализую явно, а классы с реализацией делаю internal или private чтобы ни у кого (даже мало квалифицированного) не возникало желание использовать их в обход интерфейсов.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Работа взрослых
От: · Великобритания  
Дата: 15.12.22 23:17
Оценка: 9 (1)
Здравствуйте, Qbit86, Вы писали:

vsb>>>В жаве работает. А почему в C# ошибка?

Q>Так ведь в приведённом примере на Джаве ещё хуже, там будет ошибка компиляции не на попытке вызова метода method(), а ещё на этапе попытки определить имплементора двух интерфейсов без явной реализации метода method():
Может в c# сделали и более идеологически верно, но в java более практически полезно. На практике такой конфликт дефолтных методов явно будет жуткой редкостью, лечится элементарно, зато возможность писать new MyThing().defaultMethod() или использовать var намного полезнее и требуется гораздо чаще.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[8]: Diamond inheritance
От: Codealot Земля  
Дата: 15.12.22 23:44
Оценка: :))
Здравствуйте, VladD2, Вы писали:

VD>Попробуй мыслить в терминах абстракции. Пойми, что раз ты завёл интерфейсы, то работать с классами иже не имеешь права.


Это у тебя тяжелый случай синдрома "чем больше абстракций, тем лучше".

VD>Тогда то, что данная фича не рассчитана на добавление реализации в публичный интерфейс классов тебя нисколько не удивит. Если тебе нужен публичны интерфейс класса, не реализуй его через интерфейсы, а пользуйся наследованием реализации.


В C# нет множественного наследования реализаций.

VD>Я зачастую интерфейсы специально реализую явно, а классы с реализацией делаю internal или private чтобы ни у кого (даже мало квалифицированного) не возникало желание использовать их в обход интерфейсов.


Да, ни у кого. В том числе у тех, кто намного опытнее и талантливее тебя.
Ад пуст, все бесы здесь.
Re: default interface methods. Какой все же бред.
От: stapter  
Дата: 15.12.22 23:48
Оценка:
C>Вот за каким хреном так сделали?

Если кратко, оглядываясь на причину появления аналогичного функционала в Java, то для обратной совместимости.
Данная "фича" позволяет разработчику добавлять новые методы в интерфейсы, не ломая существующие имплементации этих интерфейсов.
Re[13]: Breaking change
От: Ночной Смотрящий Россия  
Дата: 16.12.22 12:55
Оценка: -1
Здравствуйте, 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.

Трейты, миксины — это как раз та самая борьба с врожденными уродствами ООП, попытка получить наследование реализации без наследования контракта. Интерфейсы+трейты покрывают весь функционал обычного наследования, оставаясь при этом в рамках привычного ОО-полиморфизма, при этом гибче и менее проблемно.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[17]: Breaking change
От: Ночной Смотрящий Россия  
Дата: 16.12.22 12:58
Оценка:
Здравствуйте, xpalex, Вы писали:

X>Эта фича, особенно в jave, гибрид сишарповых extension methods и с++ специализации.


Оно скорее развитие, а не гибрид. Иное название сей фичи в шарпе — virtual extension methods.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[12]: Breaking change
От: Ночной Смотрящий Россия  
Дата: 16.12.22 13:06
Оценка: +1
Здравствуйте, 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.

... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[12]: Breaking change
От: Ночной Смотрящий Россия  
Дата: 16.12.22 13:20
Оценка:
Здравствуйте, Sinclair, Вы писали:

K>>Никак. Этот интерфейс для этого не предназначен.

S>Хм. А зачем тогда потребовался Enumerable.Count()?

Тут интересно посмотреть на его реализацию:
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. Последнее может быть большой проблемой, ибо работает — не трожь.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[11]: Breaking change
От: Ночной Смотрящий Россия  
Дата: 16.12.22 13:20
Оценка:
Здравствуйте, karbofos42, Вы писали:

K>Эту заглушку можно дать в виде статического хелпера.


Статический хелпер не обеспечимвает полиморфизма.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[9]: Diamond inheritance
От: Ночной Смотрящий Россия  
Дата: 16.12.22 13:33
Оценка:
Здравствуйте, Codealot, Вы писали:

VD>>Я зачастую интерфейсы специально реализую явно, а классы с реализацией делаю internal или private чтобы ни у кого (даже мало квалифицированного) не возникало желание использовать их в обход интерфейсов.

C>Да, ни у кого. В том числе у тех, кто намного опытнее и талантливее тебя.

Это прям свежий звук в проектировании софта, дизайн для тех кто опытнее и талантливее.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[10]: Diamond inheritance
От: Codealot Земля  
Дата: 16.12.22 15:50
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

НС>Это прям свежий звук в проектировании софта, дизайн для тех кто опытнее и талантливее.


Особый дизайн не нужен, просто не мешай.
Ад пуст, все бесы здесь.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.