Новые возможности C#, которые можно ожидать в ближайшее время



    В апреле 2003-его года был выпущен C# 1.2 и с тех пор все версии имели только major версию.
    И вот сейчас, если верить официальной страничке roslyn на github, в работе версии 7.1 и 7.2.

    Для того, чтобы попробовать версию 7.1 необходимо установить pre-release Visual Studio. Скачать ее можно скачать с официального сайта

    Зайдя в свойства решения можно выбрать используемую версию языка.



    Далее я хочу рассказать о понравившихся мне новых фичах языка.

    Асинхронный Main (планируется в версии 7.1)


    В наше время очень часто встречаются программы, которые почти полностью асинхронные.
    До сих пор Main мог быть void или int. И мог содержать в себе аргументы в виде массива строк. Сейчас добавятся несколько новых перегрузок:

    public static Task Main();
    public static Task<int> Main();
    public static Task Main(string[] args);
    public static Task<int> Main(string[] args);

    Так как CLR не поддерживает асинхронные entrypoints, то компилятор сам создает boilerplate code (то есть автоматически вставляет какой-то дополнительный код, который позволяет привести void к Task)
    Примерно такой код будет сгенерирован компилятором:

    async Task<int> Main(string[] args) {
        // здесь какой-то ваш код
    }
    // этот метод сгенерируется «за кулисами» автоматически
    int $GeneratedMain(string[] args) {
        return Main(args).GetAwaiter().GetResult();
    }

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

    Литерал default (планируется в версии 7.1)


    У Visual Basic и у C# возможности примерно одинаковые. Но в самих языках есть определенные различия. Скажем у C# есть null, а у VB.NET есть Nothing. Так вот, Nothing может конвертироваться в любой системный тип, представляя собой значение по умолчанию для этого типа. Понятно, что null этого не делает. Хотя, значением по умолчанию вполне может быть и null.

    Сейчас уже есть и применяется выражение default(T). Рассмотрим пример его применения. Допустим, что у нас есть метод, который принимает массив строк в качестве параметра:

    void SomeMethod(string[] args)
    {
                
    }

    Можно выполнить метод и передать ему значение массива строк по умолчанию так:

    SomeMethod(default(string[]));

    Но зачем писать default(string[]), если можно просто написать default?

    Вот, начиная с C# 7.1 и можно будет пользоваться литералом default. Что еще можно будет с ним сделать, кроме как передать значение по умолчанию в метод? Например, его можно использовать в качестве значения необязательного параметра:

    void SomeMethod(string[] args = default)
    {
                
    }

    или инициализировать переменную значением по умолчанию:

    int i = default;

    А еще можно будет проверить не является ли текущее значение значением по умолчанию:

    int i = 1; 
    if (i == default) { }   // значением по умолчанию типа int является 0
    if (i is default) { }     // точно такая же проверка
    

    Что нельзя будет сделать? Следующие примеры того как литерал default использовать нельзя:

    const int? y = default;  
    if (default == default)
    if (default is T) // оператор is нельзя использовать с default
    var i = default
    throw default
    default as int; // 'as' может быть только reference тип

    Readonly ref (планируется в версии 7.2)


    При отправке структур в методы в качестве by value (по значению) происходит копирование объекта, что стоит ресурсов. А иногда бывает необходимость отправить только значение ссылочного объекта. В таком случае разработчики отправляют значение по ссылке ref для того, чтобы сэкономить память, но при этом пишут в комментариях что значение изменять нельзя. Особенно часто это происходит при математических операциях с объектами vector и matrix.

    Понятный пример того что нас ждет:

        static Vector3 Add (ref readonly Vector3 v1, ref readonly Vector3 v2)
        {
            // так нельзя!
            v1 = default(Vector3);
    
            // и так нельзя!
            v1.X = 0;
    
            // так тоже нельзя!
            foo(ref v1.X);
    
            // а вот теперь нормально
            return new Vector3(v1.X +v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
        }

    Эта функция пока что в состоянии прототипа, но я уверен в ее необходимости. Одним из предлагаемых вариантов является использование ключевого слова in вместо ref readonly

        static Vector3 Add (in Vector3 v1, in Vector3 v2)
       {
           return new Vector3(v1.X +v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
       }

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

    Методы интерфейсов по умолчанию (планируется в версии 8.0)


    Эти методы мы увидим не так скоро.

    Представьте себе, что есть какой-то класс или структура, которые реализуют интерфейс. И вот они смогут унаследовать реализацию одного метода из интерфейса(!) или должны будут реализовать свою версию этого метода.

    Уже само понятие реализации в интерфейсе звучит довольно странно. Хотя у Java 8 есть методы интерфейсов по умолчанию, и разработчики использующие C# тоже захотели себе подобный функционал.

    Этот функционал позволит автору API изменить код метода для всех уже существующих реализаций интерфейса.

    На примере должно быть понятно. Допустим у нас есть такой вот интерфейс у которого метод SomeMethod содержит реализацию:

    interface IA
    {
     void SomeMethod() { WriteLine("Вызван SomeMethod интерфейса IA"); }
    }

    Теперь создадим класс, которые реализует интерфейс:

    class C : IA { }

    И создадим экземпляр интерфейса:

    IA i = new C();

    Теперь мы можем вызвать метод SomeMethod:

    i.SomeMethod(); // выведет на экран "Вызван SomeMethod интерфейса IA"

    Предположительные имена кортежей (планируется в версии 7.1)


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

    Пример:

    Вместо того, чтобы писать (f1: x.f1, f2: x?.f2) можно просто написать (x.f1, x?.f2).
    Первый элемент кортежа в таком случчае получит имя f1, а второй f2
    Сейчас же кортеж стал бы неименнованным (к элементам которого можно обратиться только по item1, item2 ...)

    Предположительные имена особенно удобны при использовании кортежей в LINQ

    // c и result содержат в себе элементы с именами f1 и f2
    var result = list.Select(c => (c.f1, c.f2)).Where(t => t.f2 == 1);

    Отрицательные стороны

    Основным недостатком введения этого функционала является совместимость с C# 7.0. Пример:

    Action y = () => M();
    var t = (x: x, y);
    t.y(); // ранее был бы выбран extension method y(), а сейчас будет вызвана лямбда

    Но с этим небольшим нарушением совместимости можно смириться, так как период между выходом 7.0 и 7.1 будет небольшим.

    Собственно, уже сейчас Visual Studio при использовании 7.0 предупреждает, что
    Tuple element name 'y' is inferred. Please use language version 7.1 or greater to access an element by its inferred name.

    Полный код примера:

    class Program
    {
       static void Main(string[] args)
       {
           string x = "demo";
           Action y = () => M();
           var t = (x: x, y);
           t.y(); // ранее был бы выбран extension method y(), а сейчас будет вызвана лямбда  
       }
    
       private static void M()
       {
           Console.WriteLine("M");
       }
    }
    
    public static class MyExtensions
    {
       public static void y(this (string s, Action a) tu)
       {
           Console.WriteLine("extension method");
       }
    }

    Если вы используете .NET 4.6.2 и ниже, то для того чтобы попробовать пример вам необходимо установить NuGet пакет System.ValueTuple.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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


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

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

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

                                        • +1

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

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

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


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

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


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

                                              • 0

                                                Есть вариант реализовывать такие методы явно, т.о. не будет проблемы ромба.
                                                Тогда ответом на вопрос "функция какого интерфейса вызовется?" — ошибка компиляции, если 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


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

                                                • 0

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

                                                  • +2

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


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

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

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

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

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

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


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

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

                                                  Методы для интерфейсов и сейчас без проблем добавляются через 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;
                                                  }

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

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

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

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

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

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


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


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

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

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

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


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


                                                              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) };
                                                              }
                                                              • 0

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


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


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

                                                                • 0

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

                                                                  • +1
                                                                    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

                                                                    • +2

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


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

                                                              • +1
                                                                Следует рассматривать методы с реализацией не как часть протокола, а как заранее определенную возможность работы с протоколом ( как extension method), которую можно переопредлить для конкретных имплементаций.
                                                            • +7
                                                              «Почему in, ведь ref readonly понятнее? А потому что in короче.» Уверены?) а можем, потому что in — противоположность out?) «Передан по ссылке БЕЗ возможности записи» против «Передан по ссылке ОБЯЗАТЕЛЬНО для записи»
                                                              • 0
                                                                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.
                                                                  • +2
                                                                           string x = "demo";
                                                                           Action y = () => M();
                                                                           var t = (x: x, y);
                                                                           t.y(); // ранее был бы выбран extension method y(), а сейчас будет вызвана лямбда  
                                                                    

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

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

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

                                                                        Где-то читал, что в 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. На мой взгляд лучше.

                                                                        • +2

                                                                          На самом деле, если это интерфейс, то он по определению не должен содержать полей. 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();
                                                                              }
                                                                          }
                                                                        • 0

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

                                                                          • 0
                                                                            Будет здорово в 8.0 увидеть Extension Everything
                                                                            • +2
                                                                              Лучше бы, наконец, records, доделали. Анонсированные фичи, как-то не особо впечатляют.
                                                                              • –2
                                                                                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)
                                                                                

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

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


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

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

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