Как стать автором
Обновить

Комментарии 73

Язык развивается, это всегда приятно. С другой стороны, я рад что начал изучать C# достаточно давно и теперь все эти новые фичи поступают малыми порциями и легко усваиваются. А каково будет только начинающему изучать язык через пару лет новичку? Он захлебнется в куче этих фич… Да даже если я сейчас начну изучать тот же С++, там та же картина? Или я заблуждаюсь?

Там вообще жесть :)

Насколько я могу судить, в c++ еще хуже
Да ладно, стандарты выходят раз в три (?) года, да и чем-то прям глобальным был только 11й, осилить несколько фич раз в три года не настолько невозможно же, да?
В онгоуинге освоить вообще нормально, и даже не хватает, приходится boost использовать и иже с ним.
А вот осваивать современный c++ с нуля, мне кажется, жесть
А каково будет только начинающему изучать язык через пару лет новичку?
В точку.
Прям как с питоном ситуация.

А что с Питоном?

Ну тоже появляются новые вещи, в том числе и в синтаксисе. Всякие yield from, f-strings, async/await, dict literals, raise from.

И теперь придется учить больше, зато всяких костылей теперь меньше.
Да, а что с питоном?
А зачем нужны методы интерфейса с собственной реализацией?

Т.е. я создам интерфейс сразу с реализацией, потому что не хочу плодить лишние классы, а любой другой реализации надо будет переопределять метод?
И почему бы не использовать для этого абстрактный класс с виртуальными методами…
Наверное потому, что это ещё один класс. А хотят обходится без него в несложной архитектуре
Раз такие дела, то может и без интерфейса обойтись? Интерфейсы хорошо бы создавать с целью, а не потому, что модно!

Потому что множественное наследование запрещено. А методы по умолчанию позволяют расширять legacy API не ломая старый код

Нельзя наследоваться от двух или более абстрактных классов, сейчас самое близкое к тому, что это даст — это два интерфейса с методами расширения.
Потому что dependency injection на абстрактном классе не сделать

А в чем, собственно, проблема-то?

Можно и на абстрактном классе. У М.Симана в его книжке про DI в .NET об этом есть сноска.

Как минимум, чисто технически, можно и обычный неабстрактный класс внедрять.

Возник тот же вопрос. Тема реализации методов в интерфейсе не раскрыта ((
Интересно чем интерфейсы с реализацией методов будут отличаться от множественного наследования C++?

У интерфейса не может быть нестатических полей.

Наоборот, интерфейс не может содержать статические поля!

Думаю, отличается правилами вызова. Например:


  1. Один интерфейс с реализованным методом — класс использует реализацию интерфейса.
  2. Переопределяем метод у класса — класс использует свою реализацию. Каст к интерфейсу вызывает реализацию из интерфейса.
  3. Несколько интерфейсов с реализацией — работает как будто несколько интерфейсов реализованно явно.
Каст к интерфейсу вызывает реализацию из интерфейса.

Если бы все было именно так — то в этой фиче не было бы ни малейшего смысла, потому что сейчас именно так работают Extension Methods. Даже само название "Default Interface Methods" подразумевает, что реализация из интерфейса используется только если она не переопределена в классе.

Выглядит как будто кто-то хочет протянуть аналог множественного наследования
хотя отсутствие мультинаследования — именно одно из тех ограничений, которые помогают часто писать более чистый, понятный и масштабируемый код
А это кажись стырили у Java :) Там это было нужно для нормальной работы стримов со старым кодом.
НЛО прилетело и опубликовало эту надпись здесь

Это нужно для той же цели, для которой сейчас делаются методы-расширения, но с возможностью переопределить такой метод для более эффективной реализации.


Посмотрите сколько явных проверок типа сделано в реализации Enumerable — каждую из них можно было бы заменить переопределением виртуальной функции. Не уверен насчет скорости работы — но расширяемее это бы получилось однозначно.

интерфейс с собственной реализацией я думаю больше нужен самим авторам с#- чтобы легко добавлять новый функционал к старым интерфейсам и не переписывать при этом все библиотеки их использующие.
ИМХО, в интерфейсе не должно быть реализации

Хм, снова возвращаемся к проблеме ромбовидного наследовния? И для решения постоянно прийдётся писать explicit?

Отсутствие полей у интерфейсов, а также тот факт что реализация интерфейса — всегда виртуальная в терминах С++ снимают главные проблемы ромбовидного наследования. Остается только проблема конфликтов имен.

Остается только проблема конфликтов имен.

Ну так это одна из основополагающих и ключевых проблем, разве нет?
Если есть два интерфейса IA, IB с одинаково описанной функцией void Function(), но которая реализует разную функциональность, то


public class Test: IA, IB { /* без реализации - ведь она "больше не нужна" */ }
....
var a = new Test();
a.Function(); // <= функция какого интерфейса вызовется?

Если не добавлять новых методов в старые интерфейсы — то такую ситуацию должен отловить и исправить разработчик класса Test.


К сожалению, одно из применений этой фичи — как раз добавление новых методов в существующие интерфейсы. И вот тут и правда может быть проблема.

Есть вариант реализовывать такие методы явно, т.о. не будет проблемы ромба.
Тогда ответом на вопрос "функция какого интерфейса вызовется?" — ошибка компиляции, если Function явно не реализована в Test. А вызов Function конкретного интерфейса будет доступен через каст.
Собственно в примере как раз это и происходит


IA i = new C();

Правда тогда не совсем ясна польза от таких финтов. Метод расширения в таком случае может быть предпочтительнее. Хотя это зависит конечно.


P.S. Немного погуглил и похоже так и планируется сделать.
Вот полный пример


interface IA
{
    void M() { WriteLine("IA.M"); }
}

class C : IA { } // OK

IA i = new C();
i.M(); // prints "IA.M"
new C().M(); // error: class 'C' does not contain a member 'M'

https://github.com/dotnet/csharplang/blob/1918cb06e1c8b79e024c688d1c4159046479276d/proposals/default-interface-methods.md


Кстати там есть еще несколько интересных моментов. Обсуждают ряд вопросов, вроде статических, виртуальных или закрытых членов интерфейсов и т.д..

Не пугайте так! Очевидно же, что виртуальные члены в интерфейсах смысла не имеют (они же и так все по умолчанию виртуальные). А вот модификатор sealed разрешили, но не потому что он кому-то позарез нужен, а просто из желания не запрещать ничего лишнего.

Почитаю из дома....


Но тут как раз вопрос: зачем создавать проблему, а потом доблестно её преодолевать?!
Борьба с ветряными мельницами какая-то — сначала (в первой редакции языка C#) отказываемся от ромба и пафосно об этом заявляем, а, спустя немного времени, возвращаемся к исходному и уже начинаем воротить костыли на костыли, чтобы вернуть ромб и — пафосно заявляем, насколько крутую фичу добавили…
Вообщем — посмотрим когда фича выйдет и… воздержимся от использования… Во избежание. :D

Я думаю это попытка воплотить typeclass из Haskell.
Самый простой пример:
interface Eq {
  bool Equal(a, b) { return !NotEqual(a, b);}
  bool NotEqual(a, b) {return !Equal(a, b);}
}

Таким образом для реализации интерфейсов у нас минимальный набор операций для реализации это или Equal или NotEqual. В нек-ых случаях может оказаться, что операцию NotEqual реализовать проще чем Equal, а оставшуюся операцию мы получаем «бесплатно».
Имхо это плохой пример.

Во-первых, зачем нужен отдельный метод NotEqual, когда можно написать !Equal(a, b)? Если вдруг проще вычислить неравенство, можно просто инвертировать это значение перед возвратом из метода.

Во-вторых, добавляя такой интерфейс к классу, мы по умолчанию получаем возможность свалиться в бесконечную рекурсию. Компилятор уже не напомнит, что мы забыли реализовать «нормальное» сравнение объектов. Довольно дорогая цена за сомнительное удобство!

Пример действительно плохой, со сравнениями всё слишком тривиально.


Лучше рассмотреть, скажем, тайпкласс для монад (для некоторых удобнее написать join :: m (m a) -> m a, для некоторых — bind aka >>= :: m a -> (a -> m b) -> m b. Или, скажем, Traversable, или вообще Foldable, в котором вообще мясо, и минимальным необходимым определением является одна из двух функций, остальную дюжину функций этого тайпкласса можно вывести из них.

Это позволяет использовать интерфейсы, как некое подобие trait'ов.

Методы для интерфейсов и сейчас без проблем добавляются через extensions-методы:


public interface IRectangle
{
    int Width { get; }
    int Height { get; }
}

public static class IRectangleExtensions
{
    public static int GetArea(this IRectangle r) => r.Width * r.Height;
}

Как по мне, так вполне вменяемое применение. Просто упростят реализацию.
И будет логично, что переопределить метод интерфейса будет невозможно.

И будет логично, что переопределить метод интерфейса будет невозможно

Как раз-таки в этом и вся соль, что можно переопределить метод по умолчанию.
Получается, мы делаем интерфейс и сразу extension метод к нему, но который можно переопределять в реализациях… Функционально, но засоряет интерфейс, на мой взгляд. Лучше б какие-нибудь виртуальные extension методы запилили.
Вопрос даже не в том что зачем метод интерфейсу… Вопрос в том что они ломают концепцию того что интерфейс по своей сути — контракт. Описание протокола взаимодействия. Не может быть в описании реализация. Они ломают концепцию. На мой взгляд, это лютая жесть

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

Абстрактные классы? Описание протокола взаимодействия — это, считай, документация ожиданий в реализации. Не более того

Зайду с другой стороны.


В чем вы видите принципиальное отличие между двумя интерфейсами ниже и почему первый — это нормально, а второй — лютая жесть и поломанная концепция?


interface A {
  void Foo(int x = 42);
}

interface B {
  void Foo(int x);
  void Foo() => Foo(42);
}
А что, можно в первом случае = 42 написать в интерфейс?

Вообще-то да. Более того, если класс используется через интерфейс, как часто бывает при использовании DI, то нигде кроме интерфейса писать = 42 не имеет смысла.

Ну, довольно подозрительное поведение, на мой взгляд. Как впрочем и везде с дефолтными аргументами.
Если я в классе (в реализации интерфейса) установил другое значение, то теперь от типизации переменной будет зависеть какое значение придёт по умолчанию.
И это я молчу о потенциальной смене дефолтных значений, которое и для классов не работает без полной пересборки.

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


Во втором случае вы говорите тоже самое но с одним большим НО. Какого-то черта описание протокола взаимодействия вызывает какой-то метод. Ваш пример защищает концепт мой пример говорит что это кривая лажа:


interface A {
  void Foo(int x = 42);
}

interface B {
  void Foo(int x);
  void Foo() { Foo(Unity.Resolve<DatabaseContext>().Set<Entity>().First(x => x.Id >=1000).Offset) };
}

Ваш пример говорит только он том, что он сам — кривая лажа.


Писать фигню можно на любом языке и с использованием любых инструментов, от написания фигни не застрахован ни один язык (кроме, возможно, HQ9+).


А потому оценивать языковые возможности надо по их лучшим проявлениям, а не по худшим.

Окей, убедите. Приведите пример вызова методов из интерфейсов, когда это было бы оправдано с точки зрения "было плохо, стало лучше" и оправдайте решение.

The principal motivations for this feature are
  • Default interface methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.
  • The feature enables C# to interoperate with APIs targeting Android (Java) and iOS (Swift), which support similar features.
  • 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 (http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf).

https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md#motivation

Например ToList() который сейчас экстеншн метод к IEnumerable<>. Сейчас если я делаю свой класс, имплементящий IEnumerable, то метод ToList() пройдет по моему IEnumerable<> и если элементов много, то создаст в процессе много массивов, копируя их туда-сюда в процессе.
Если в моей реализации заранее известно количество элементов, то я мог бы в принципе сделать оптимальнее, только это очень убого будет. Нужно будет экстеншн MyToList() делать, который проверит тип IEnumerable<>, является ли он моей реализацией, вызовет оптимальную реализацию если да, или стандартную если нет.


Ботомлайн — эту фичу уже можно делать костыльно с помощью экстеншн методов. Те кому не нравится фича, должны ненавидеть и экстеншн методы, потому что они те же самые методы на интерфейсах, только хуже.

Следует рассматривать методы с реализацией не как часть протокола, а как заранее определенную возможность работы с протоколом ( как extension method), которую можно переопредлить для конкретных имплементаций.
«Почему in, ведь ref readonly понятнее? А потому что in короче.» Уверены?) а можем, потому что in — противоположность out?) «Передан по ссылке БЕЗ возможности записи» против «Передан по ссылке ОБЯЗАТЕЛЬНО для записи»
One of proposed syntaxes is to use existing in keyword as a shorter form of ref readonly. It could be that both syntaxes are allowed or that we pick just one of them.
       string x = "demo";
       Action y = () => M();
       var t = (x: x, y);
       t.y(); // ранее был бы выбран extension method y(), а сейчас будет вызвана лямбда  

Вообще, по-моему, это хороший рассадник граблей. Очень неявно и для неопытных программистов может быть сложно определить источник проблемы.

Мне кажется, что то же самое было бы с анонимным объектом (а-ля new { x, y }.y()), так что это не новая грабля :)

Имхо, тут проблема не в новичках, а в нарушении обратной совместимости. На C# пишется много крупных enterprise-проектов, для которых обновление компилятора с возможностью незаметно сломать логику — слишком большой риск.

Где-то читал, что в C# 7 хотели сделать not null reference types. Предлагали использовать


string! str1 = "123";  //not null
string! str2;          //ошибка, not-null типу присваивается null
string str3;           //стандартное поведение

Как я понял, это не сделали и это трансформировалось в Static null checking in C# в C# 8.


DefaultInterfaceMethods выглядят каким-то жутким хаком и нарушением логики — если это интерфейс, то он по-определению не должен содержать реализаций! Тогда это приведет к множественному наследованию со всеми вытекающими проблемами. Если есть необходимость во множественном наследовании, можно пойти путем, как многие языки — mixins. На мой взгляд лучше.

На самом деле, если это интерфейс, то он по определению не должен содержать полей. Default Interface Method принципиально ничем не отличается от пары из метода-расширения и доп. интерфейса, только пишется проще, а работает быстрее:


interface A {
    void Foo(int x);
    void Foo2() { Foo(2); }
}

// Почти эквивалентно

interface A {
    void Foo(int x);
}

interface AExt {
    void Foo2();
}

static class AExtStatic {
    public static void Foo2(this A obj) {
         var objext = obj as AExt;
         if (objext == null)
           obj.Foo(2);
         else
           objext.Foo2();
    }
}

Трейты будут?

Лучше бы, наконец, records, доделали. Анонсированные фичи, как-то не особо впечатляют.
static Vector3 Add (ref readonly Vector3 v1, ref readonly Vector3 v2)


static Vector3 Add (in Vector3 v1, in Vector3 v2)


Почему in, ведь ref readonly понятнее? А потому что in короче.

Можно еще короче:
static Vector3 Add (=Vector3 v1, =Vector3 v2)

Немного непривычно, но глаз быстро привыкнет.
int $GeneratedMain(string[] args)

Я как и многие ожидал этот функционал еще в 7-ой версии языка.


А можно пример, зачем это может быть нужно?

Чтобы не писать одну и ту же обертку над Main каждый раз, когда есть необходимость использовать асинхронный код в основном потоке консольного приложения.

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