Что нового в C# 8?

    «Восьмерка» еще даже не вышла RTM а я уже пишу про нее пост. Зачем? Ну, основная идея что тот, кто предупрежден — вооружен. Так что в этом посте будет про то что известно на текущий момент, а если это все подстава, ну, поделом.



    Nullable Reference Types


    Я не знаю, в курсе вы или нет, но дизайнеры всех современных языков «продолбали» как минимум несколько важных аспектов. Один из основных продолбов — это неинициализированные объекты, указатель на которые имеет значение NULL в С и nullptr в современном С++. Другой аспект — это zero-terminated strings, когда длина строки вычисляется за О(n). Но мы сейчас говорим за null.


    C# это конечно тоже зацепило в разных ипостасях, вот например:


    public class Person
    {
      public string FirstName { get; set; }
      public string LastName { get; set; }
      public string MiddleName { get; set; }
      public Person(string first, string last, string middle) =>
        (FirstName, LastName, MiddleName) = (first, last, middle);
      public string FullName =>
        $"{FirstName} {MiddleName[0]} {LastName}";
    }
    

    Пример выше очень хорошо иллюстрирует проблему. У человека может быть отчество но у меня, например, в паспорте его нет и следовательно непонятно что писать в это поле. Дефолтное значение строки в C# это null. Если интерполировать это значение в пустую строку ($ за кулисами делает string.Format()), ничего страшного не будет. Но если вы попробуете получить первую букву null-значения, вы получите исключение NRE (NullReferenceException).


    Кардинального решения этой проблемы нет, т.к. если внезапно запретить null, придется написать 100500 статических проверок на инициализацию всех объектов во всех брэнчах конструктора. Или перейти полностью на Optional типы что, собственно, тоже не совсем решает проблему.


    Системы статического анализа вроде Решарпера уже давно пытаются как-то облегчить участь разработчика, предупреждая о возможных косяках. Собственно для этого придумали аннотации (NuGet пакет JetBrains.Annotations) которые можно добавить в проект вот таким вот образом:


    public class Person
    {
      public string FirstName { get; set; }
      public string LastName { get; set; }
      [CanBeNull] public string MiddleName { get; set; }
      public Person(string first, string last, [CanBeNull] string middle) =>
        (FirstName, LastName, MiddleName) = (first, last, middle);
      public string FullName =>
        $"{FirstName} {MiddleName[0]} {LastName}";
    }
    

    Код выше заставляет системы статического анализа ругаться на возможный NRE в точке MiddleName[0].




    Но Microsoft… как всегда берет то, что делает другие и банально копирует. Вообще есть шутка, что в долгосрочной перспективе VS просто скопирует все фичи Решарпера. А потом запретит плагины. Поэтому хорошо что есть райдер.


    Короче, МС естественно не стали менять язык. Точнее как не стали, они конечно его поменяли. Вместо решарперных аннотаций, в C#8 можно написать вот так:


    #nullable enable
    

    Просто написав от эту штуку наверху файла вы меняете поведения компилятора. Теперь, при написании чего-то вроде


    var p = new Person("Dmitri", "Nesteruk", null);
    

    вы получите следующий warning:

    1>NullableReferenceTypes.cs(26,48,26,52): warning CS8625: Cannot convert null literal to non-nullable reference or unconstrained type parameter.
    

    Да-да, просто предупреждение, а не ошибку (хотя treat warnings as errors никто не отменял).


    Ну да ладно, а что дальше? А дальше нам хочется как-то все-таки сказать C#, что теоретически MiddleName таки может быть null поэтому доступ с индексом [0] тоже нужно проверять.


    Для этого мы меняем поле на вот такое:


    public string? MiddleName;
    

    Хмм, что это? Кому-то может показаться что string? эквивалентно Nullable<T>, но напомню что в этом типе, T : struct, так что очевидно это что-то другое. Этот вопросик — это всего лишь подсказка компилятору, т.к. по факту тип поля все еще обычный string.


    Теперь компилятор выдаст вам еще один варнинг на доступ по индексу ноль. И чтобы оно заработало вам придется как-то перестраховаться, например написав:


    public string FullName =>  $"{FirstName} {MiddleName?[0]} {LastName}";
    

    Теперь самый важный вопрос: что же поменялось в IL? С точки зрения исполняемого кода — ничего! Но с точки зрения метаданных поменялось конечно: теперь в типе который использует nullable аннотации все типы которые являются nullable проаннотированы атрибутом [Nullable]. Сделано это по понятным причинам: чтобы потребители вашего кода могли использовать ваши аннотации.


    Чувствительность к проверкам


    Аннотации в C#8 работают в какой-то мере как Котлиновские смарт-касты. Иначе говоря, если у меня есть апи который выдает nullable-тип


    string? s = GetString();
    

    То конечно при попытке достучаться до первой буквы я получу warning. Но если я напишу так:


    if (s != null){  char c = s[0];}
    

    то варнингов не будет! Компилятор понимает, что мы сами сделали проверку и поэтому не стоит лишний раз волноваться. Насколько глубоко компилятор копает подобные сценарии я не проверял.


    Предотвращение лишних проверок


    Есть два способа отключить проверки на null. Первый — это просто писать код без «вопросиков», тем самым констатировав тот факт что все твои поля, параметры и так далее вообще ни при каких условиях null-ами быть не могут.


    Второй подход — это явно сказать компилятору что в этой точке проверка не нужна. Вот несколько примеров:


    • (null as Person).FullName конечно выдаст нам warning

    • (null as Person)!.FullName warning уже не выдаст, т.к. мы явно просим не проверять выражение

    • (null as Person)!!!!!!!!!!!!!.FullName тоже является валидным выражением и тоже отключает проверки

    • (null as Person)!?.FullName тоже валидно и все же делает в этом случае проверку на null; примечательно что обратное использование, ?!, не скомпилируется


    Проверки в либах


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


    Type t = Type.GetType("abracadabra");Console.WriteLine(t.Name);
    

    и не получить никакого предупреждения. Я-то знаю что Type.GetType() возвращает null когда даешь невалидное название типа, но поскольку BCL пока еще не размечена nullable аннотациями, компилятор это съедает.


    И нет, мы не можем «форсировать» подобные проверки кодом вроде


    Type t = Type.GetType("abracadabra");
    Type? u = t;
    Console.WriteLine(u.Name);
    

    Код выше все равно не выдаст предупреждение. Очевидно, компилятор считает что t != null, следовательно u тоже не может быть равно null, сколько его не декорируй.


    Итого


    Nullable reference types — сомнительной полезности фича, которую еще рано использовать. Оригинальности в ней мало. В долгосрочной перспективе она, конечно, должна помочь нам как-то бороться с рисками nullability но глобально она проблему, как вы понимаете, не решает.


    Индексы и Диапазоны


    На матстатистке есть такое хороше упражнение: брать диапазон чисел, обладающих тем или иным распределением, преобразовывать его, и потом считать статистику по результату. Например, если X~N(0,1), чему равны E[X²] и V[X²]?


    Диапазоны это очень хорошо, но дизайнерами сишарпа явно хотелось получить диапазоны в стиле Питона, а для этого пришлось ввести понятие «с конца».


    Итак, у нас за кулисами появляются два новых типа: Index и Range.


    Index


    Вы никогда не задумывались, почему это индекс в массив обязательно int а не uint? Все просто: этот ваш пресловутый индекс это просто отступ от указателя на начало массива (привет Си). Поэтому он теоретически может быть отрицательным, хотя конечно в C# запись x[-1] лишена всякого смысла.


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


    В C# пошли другим путем и ввели новый синтаксис. Но, все по порядку. Для начала, ввели новый тип под названием Index который представляет индекс элемента в массиве, строке или вашей собственной коллекции:


    Index i0 = 2; // implicit conversion
    

    Индекс выше, как вы понимаете, будет ссылаться на третий элемент с начала той или иной коллекции.


    У самого типа Index есть два свойства (зачем сделали свойства а не поля — тот еще вопрос):


    • Value, то есть сколько элементов нужно отсчитать

    • IsFromEnd — булевое значение, показывающее, нужно ли отсчитывать от конца коллекции а не от начала


    Структуру можно инициализировать просто вызвав конструктор:


    Index i1 = new Index(0, false);
    

    Код выше, как вы поняли, берет первый элемент с начала. А вот последний элемент (то есть нулевой, но с конца) можно взять вот так:


    var i2 = ^0; // Index(0, true)
    

    Опаньки! Многие из вас наверное хотели, чтобы оператор ^ сделали возведением в степень (мне как математику эта идея комфортнее, LaTeX, все такое), но по факту это теперь специальный синтаксис для создания индекса «с конца».


    Встроенные типы, такие как массивы или строки, конечно же поддерживают индексер (operator this[]) для типа Index, так что смело можно писать


    var items = new[] { 1, 2, 3, 4, 5 };
    items[^2] = 33; // 1, 2, 33, 4, 5
    

    Range


    Следующий кусочек этого паззла — это тип Range, который представляет из себя линейный, направленный строго по возрастанию диапазон индексов, строго с шагом 1. Для него тоже введен новый синтаксис:


    X..Y
    

    Что означает «все элементы от X включительно до Y». При этом, включает ли диапазон значение с индексом Y или нет — нельзя сказать не посмотрев на Y. Об этом очень скоро.


    Итак, вот несколько примеров:


    • var a = i1..i2; // Range(i1, i2) — полноценный диапазон с началом и концом

    • var b = i1..; // Range(i1, new Index(0, true)); — диапазон от i1 и до конечного элемента

    • var c = ..i2; // Range(new Index(0, false), i2) — диапазон от самого первого элемента и до индекса i2

    • var e = ..; — вообще весь диапазон, то есть от первого и до последнего элемента

    • Range.ToEnd(2); — эта и подобные статические функции — как раз то, что использует компилятор за кулисами; все это можно лицезреть, если открыть сборку в dotPeek, ilSpy или другом декомпиляторе


    Включается ли конечный элемент?


    Окей, знаете что в С++ толстым слоем разбросано неопределенное поведение (undefined behavior?). Ну так вот, в реализации Range в C# тоже затаился сюрприз. Суть примерно в следующем: последний элемент включается только если он взят «с конца».


    Представьте массив x = {1, 2, 3}. Если взять x[0..2] вы получите {1, 2}, а вот если взять x[..2] или например x[..], вы получите {1, 2, 3}.


    Это немного выносит мозг т.к. элементы x[2] и x[^1] — это одинаковые элементы, но семантика их поведения как часть Range-а разная!


    Еще немного семантики


    Во-первых, диапазон X..Y будет работать только если X <= Y (надеюсь вы включили лигатуры?). Если же вы решили сделать нисходящий диапазон (например 7..3), то вы просто получите ArgumentOutOfRangeException. Это очень неприятно т.к. часто хочется получить индексы массивы как некоторые выходные значения из функций, и проверять что диапазон восходящий ну совсем не хочется.


    Во-вторых, «шаг» не включен в спеку, то есть нельзя написать 1..2..100 и получить только нечетные числа. А было бы удобно. Эта фича реализована в языках вроде MATLAB.


    Range ведет себя в стандартных типах вот так:


    • В массивах, он дает копию подмассива, прям копируя каждый элемент

    • В строках происходит вызов Substring(). Строки в C# иммутабельные так что создается новая строка.

    • На коллекциях можно вызывать AsSpan() передавая ему Range.

    • В Span тоже можно засунуть RangeSpan.Slice(), получив под-диапазон.


    Конечно, вы можете также запилить поддержку Index/Range в своих собственных типах с помощью переопределения operator this[]. При этом нужно понимать, что не во всех коллекциях понятие «с конца» работает хорошо. Например, если у вас односвязный список (singly linked list), то брать что-то «с конца» — плохая идея.


    Итого


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


    И да, решарпероводам будет много всяких веселых инспекций:




    Default Interface Members


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


    Ну типичная мотивация такая. Вот допустим вы хотите сделать Enumerable.Count() — его конечно можно делать через while (x.MoveNext()) но это немного бредово для коллекций, чья длина известна заранее. Поэтому мы втыкаем в Count() проверки типа:


    if (x is IList<T> list)
      return list.Count;
    

    Логично? А как насчет IReadOnlyList<T>, для него такая оптимизация будет? Что значит «не сделали»?!?


    Ситуация, приведенная выше наводит нас на вынужденное, очень грубое нарушение open-closed principle да и других принципов SOLID, т.к. делать проверки на все возможные типы ради оптимизации — это провальная затея.


    А что можно сделать? Ну, можно было бы, чисто теоретически, как-то взять и добавить реализацию Count(), специфичную для IReadOnlyList<T>, прямо в интерфейс, возможно как метод расширения. Только это тоже не сработает! Откуда мы знаем что экстеншн-метод IReadOnlyList<T>.Count() является перегрузкой метода IEnumerable<T>.Count()? Правильно, не знаем! Нужен другой подход.


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


    Тонкости использования


    Но давайте для начала посмотрим на более приземленный пример:


    public interface IHuman
    {
      string Name { get; set; }
        
      public void SayHello() 
      {
        Console.WriteLine($"Hello, I am {Name}");
      }
    }
    
    public class Human : IHuman
    {
      public string Name { get;set; }
    }
    

    Пример выше искусственнен, но обязателен для понимания того, что вот так писать нельзя:


    Human human = new Human() { Name = "John" };
    human.SayHello(); // will not compile
    

    Странно да? Вроде бы, валидный код. На самом деле нет — дело в том, что конкретный класс, хоть он и реализует тот или иной интерфейс, понятия не имеет о дефолтных методах этого интерфейса.


    Почему, спросите вы? Потому, что вся соль этих методов в том, чтобы добавлять их пост фактум, когда вашим интерфейсом уже пользуются. А что если за это время класс Human обзавелся собственным SayHello()? Правильно, будет конфликт.


    Поэтому дизайнеры приняли такое решение: дефолтные методы доступны только через сам интерфейс, то есть требуется явное или неявное приведение типа к интерфейсу:


    IHuman human = new Human() { Name = "John" };
    human.SayHello();
    ((IHuman)new Human { … }).SayHello();
    

    Наследование интерфейсов


    Все, что я описал выше, наводит нас на интересную мысль: если два интерфейса реализуют одинаковый метод Foo() (с дефолтной реализацией), класс может реализовать оба этих интерфейса и получить две независимые реализации Foo(), обе из которых можно вызывать:


    public interface IHuman
    {
      string Name { get; set; }
      
      void SayHello() 
      {
        Console.WriteLine($"Hello, I am {Name}");
      }
    }
    public interface IFriendlyHuman : IHuman
    {
      void SayHello()
      {
        Console.WriteLine(      $"Greeting, my name is {Name}");
      }
    }
    ((IHuman)new Human()).SayHello();
    // Hello, I am John
    ((IFriendlyHuman)new Human()).SayHello();
    // Greeting, my name is John
    

    Заметьте, что в коде выше IFriendlyHuman.SayHello() как бы должен override-ить IHuman.SayHello(), но этого не происходит! Что же нужно сделать чтобы вызов SayHello() стал действительно виртуальным? Нужно явно сказать об этом:


    public interface IFriendlyHuman : IHuman
    {
      void IHuman.SayHello()
      //   ↑↑↑↑↑↑
      {
        Console.WriteLine(      $"Greeting, my name is {Name}");
      }
    }
    

    Вот в этом случае вызов SayHello() на любом интерфейсе, будь то IHuman или IFriendlyHuman будет виртуальным и уже не важно, к какому из них вы скастовались:


    ((IHuman)new Human()).SayHello();
    Greeting, my name is John
    ((IFriendlyHuman)new Human()).SayHello();
    Greeting, my name is John
    

    Diamond Inheritance


    Естественно, в ситуации когда вы можете иметь два «честных override-а» в двух интерфейсах-наследниках породит конфликт в случае, если вы попытаетесь реализовать их оба:


    interface ITalk { void Greet(); }
    interface IAmBritish : ITalk
    {
      void ITalk.Greet() => WriteLine("Good day!");
    }
    interface IAmAmerican : ITalk
    {
      void ITalk.Greet() => WriteLine("Howdy!");
    }
    class DualNational : IAmBritish, IAmAmerican {}
    // Error CS8705 Interface member 'ITalk.Greet()' does not have a most specific implementation. Neither 'IAmBritish.ITalk.Greet()', nor 'IAmAmerican.ITalk.Greet()' are most specific.
    

    Проблема тут в том, что компилятор не может найти «более специфичный» (то есть, ниже в иерархии наследования) интерфейс для использования и, в результате равнозначности, не поймет что нужно делать если кто-то вызовет какой-нибудь IAmAmerican.Greet() — ведь по идее нужно гулять по виртуальной таблице, а куда идти-то, если варианта два?


    Итого


    Фича для писателей АПИ. Обычным пользователям скорее всего не стоит беспокоиться, особенно если вы, как я, контролируете весь свой код и вам не страшно в любой момент менять его API. Единственный реальный кейс — это когда вот прям нужно оверрайдить экстеншн-методы. У вас есть подобные юз-кейсы?


    Pattern Matching


    Эту фичу нагло крадут из F# уже на протяжении нескольких минорных релизов. Конечно — в F# ведь эта фича очень удобна, но она идет рука об руку с теми фичами, которых в С# нет, а именно алгебраические типы и функциональные списки.


    Но несмотря на это, аналогом F#ного match стал С#ный switch. В C#8 все это обрастает дополнительными возможностями.


    Property Matching


    Если у объекта есть поля или свойства, можно мэтчить по ним:


    struct PhoneNumber{
      public int Code, Number;
    }
    var phoneNumber = new PhoneNumber();
    var origin = phoneNumber switch {
      { Number: 112 } => "Emergency",
      { Code: 44 } => "UK"
    };
    

    Код выше анализирует структуру объекта phoneNumber, проверяя его поля на конкретные значения. Заметьте что у нас не switch statement а switch expression, то есть выражение которое делает возврат лямбда-образным синтаксисом.


    Несмотря на всю лаконичность, код выше — бажный, так как не отлавливает все кейсы. У дизайнеров было два выбора: либо молча сглатывать неохват паттерна делая default-init (иначе говоря, возвращать default(T) из свича у которого не замэтчился ни один паттерн) или же бросать исключение. Микрософт выбрали второе, так что если запустить код выше, мы просто получим:




    Ну и системы статического анализа кода тоже в долгу не останутся:




    Загадка


    Поскольку мы можем между фигурных скобок анализировать все что угодно, мы можем сделать и кейс, где фигурные скобки пустые:


    var origin = phoneNumber switch {
      { Number: 112 } => "Emergency",
      { Code: 44 } => "UK",
      { } => "Indeterminate",
      _ => "Missing"
    };
    

    Что это значит? Да то, что теперь есть 2 кейса: _ (подчеркивание), которое словит абсолютно любой результат, и {} которое отловит любой аргумент который не null.


    Пример выше определенно покрывает все кейсы, так что исключение мы на нем не словим. А что насчет вот такого?


    var origin = phoneNumber switch {
      { Number: 112 } => "Emergency",
      { Code: 44 } => "UK",
      { } => "Unknown"
    };
    

    Покрывает ли пример выше все варианты? Если phoneNumber это struct — то конечно да, но представим что это class. С одной стороны кто-то может сказать что да, не покрыли ситуацию с null, но… ведь есть nullable reference types!


    Вот и получается, что покрытие всех вариантов зависит не только от типа объекта но еще и от контекста компилятора.


    Рекурсивные паттерны


    Все-таки с неймингом у МС получается ооочень плохо, прям фееричный булшит. Сначала nullable reference types — масло масляное, ибо референсные типы являются nullable по определению, но термин recursive patterns — это еще на порядок хуже.


    В F#, поскольку там есть функциональные списки, можно рекурсивно проверять подсписки списка на соответствие паттернам. В C# же, этот термин значит совсем другое, причем как я уже сказал, нейминг этой фичи в C# — просто шлак.


    Если коротко, «рекурсивные паттерны» в C# это возможность углубиться внутри структуры того или иного объекта и проверить еще и поля-полей, если так можно выразиться:


    var personsOrigin = person switch {
      { Name: "Dmitri" } => "Russia",
      { PhoneNumber: { Code: 46 } } => "Sweden",
      { Name: var name } => $"No idea where {name} lives"
    };
    

    В примере выше проиллюстрированы сразу две идеи. Первая — это то, что можно углубиться в объект person.PhoneNumber и промэтчить его поле Code на значение 46. Вторая — это то что вместо мэтчинга можно задекларировать переменную и в нее записать найденное значение чтобы им с последствии пользоваться. Это тоже иногда бывает удобно, и конечно же работает в контексте этой надуманной «рекурсивности».


    Валидация


    Хорошее применение всей этой кухне: комплесная валидация разных аспектов одного сложного объекта в одном switch-е. Вот например:


    var error = person switch {
      null => "Object missing",
      { PhoneNumber: null } => "Phone number missing entirely",
      { PhoneNumber: { Number: 0 } } => "Actual number missing",
      { PhoneNumber: { Code: var code } } when code < 0 => "WTF?",
      { } => null // no error
    };
    if (error != null)
      throw new ArgumentException(error);
    

    Как видите, в коде выше идет несколько проверок: простые проверки с паттернами, плюс ключевое слово when для сложных сценариев когда идёт не сравнение с одним значением, а нечто более хитрое.


    Интеграция с проверками на типы


    Проперти-паттерны можно «поженить» с проверками на типы которые появились одним сишарпом ранее. Теперь можно проверить на тип, а потом еще и распаковать структуру:


    IEnumerable<int> GetMainOfficeNumbers()
    {
      foreach (var pn in numbers)
      {
        if (pn is ExtendedPhoneNumber { Office: "main" })
          yield return pn.Number;
      }
    }
    

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


    Деконструкция


    Надеюсь вы не забыли про деконструкцию — фичу языка которая позволяет, по сути, распаковывать тип в кортеж. Для этого тип должен реализовывать метод Deconstruct() который просто выгружает содержимое типа в out-параметры.


    Так вот, эту фичу тоже можно поженить с паттерн-мэтчингом и получить следующее:


    var type = shape switch
    {
      Rectangle((0, 0), 0, 0) => "Point at origin",
      Circle((0, 0), _) => "Circle at origin",
      Rectangle(_, var w, var h) when w == h => "Square",
      Rectangle((var x, var y), var w, var h) =>
        $"A {w}×{h} rectangle at ({x},{y})",
      _ => "something else"
    };
    

    Код выше распаковывает содержимое прямоугольника или круга в кортежные структуры, при этом есть варианты: либо заниматься паттерн-мэтчингом, либо просто деструктурировать объекты в переменные, как показано в последнем примере. Заметьте что этот процесс тоже является «рекурсивным» — у прямоугольника есть точка начала, которая деструктурируется в круглых скобках. Прочерк (_) используется для тех аспектов деструктуризации, которые нам не интересны.


    Итого


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


    Заключение


    Что, я не все фичи показал? Ну, я и не обещал, вообщем-то. Для начала хватит. Пока язык не релизнули я могу еще чуток поисследовать. А если и вы хотите поисследовать, вам потребуется VS 2019 Preview (да-да, preview версия а не RTM), .NET Core 3 (многое из описанного выше попросту не поддерживается в .NET Framework), ну и dotPeek тоже будет полезен чтобы понять, что же там за кулисами.


    У меня пока все. Продолжение (возможно) следует. ■

    Share post

    Similar posts

    Comments 225

      +22
      Так и хочется сказать «астанавитесь». Я конечно люблю c#, но что-то становится уже страшновато)
        +1

        Да фичи то ещё ладно, а вот синтаксис для них они выбирают какой-то слишком странный.
        В MiddleName?[0] знак вопроса ни к селу, ни к городу, семантики никакой. Сделали бы просто, что для переменной Nullable-типа вызов любого метода возвращает null обёрнутый в Nullable-тип возврата, если в самой переменной был null.
        ^0 как автор в статье написал, лучше бы для возведения в степень использовали.
        А тут можно было бы ~0


        термин recursive patterns — это еще на порядок хуже

        Зачем для этого вообще отдельный термин? Это же самый обычный паттерн-матчинг с нормальной деконструкцией и биндингом сматченных значений в переменные.
        Можно было бы обозвать "Pattern matching restrictions have been eased."

          +5
          Я думаю с операторами просто — унарный оператор `~` уже есть. Так что использование ~0 создало бы неоднозначность в грамматике — то ли это индексирование с конца, то ли побитное инвертирование, как раньше. Поэтому добавили унарный оператор ^. Он, кстати, никак не мешает добавить позже бинарный оператор ^ для возведения в степень.

          Знак вопроса уже используется в подобных местах, скажем x ?? -1, возвращает х, если тот не пустой, или -1. Так же логично сделали x?.foo, x?[0]. Менять поведение по умолчанию и семантику существующего кода не хотят, ввели новые операторы.
            +1

            Ну кстати ~0 для интов как раз отлично бы работал %)


            Флип всех битов как раз даёт ~0 == -1, ~1 == -2 и т.д., можно было бы сделать отрицательные индексы для "с конца", таким образом сохранить соместимость с питонистами, а остальным объяснить что надо ~0 юзать

              0

              Вы исходите из ложного предположения, что индексы начинаются с нуля.
              А ещё возможны приколы, когда вместо оэтдаемого IndexOutOfRangeException будет какой-то другой функционал.

                0

                А когда они не с нуля? (в шарпах не шарю)

                  0
                  Можно создать массив с ненулевым начальным индексом (например начинающийся с 1 и длиной в 2 элемента) Array.CreateInstance в помощь. Тогда Exception будет бросаться одинаково при доступе к 0 и к 3 элементам.
                    0

                    А что тогда будет делать ^0 ?

              –1

              Про унарный оператор ~ v0s меня уже опередил. С этим как раз ничего менять не пришлось бы. Технически бинарный оператор ^ добавить то можно, но вряд ли одному символу станут придавать настолько разный смысл в зависимости от кол-ва операндов.


              А x?. по мне всё равно дико смотрится, особенно для тех, у кого есть опыт работы с Ruby, где существует совершенно логичная конвенция, что x? всегда возвращает boolean, ну типа active? вместо isActive. Думаю, всё равно в итоге нормальную Maybe монаду завезут и эти? после x станут deprecated.

                +3
                ?.. Присутствует и в других языках программирования и делает то же самое
              0
              В kotlin подобный же синтаксис. А больше всего раздражает там, когда вызов или свойство класса из java библиотеки идёт, везде эти вопросы тыкать надо. По мне, ну не вылетает исключение, но все равно, при необходимости выполнение идёт не в то русло и попробуй отловить. Спорная это вещь. И думаю они явно на kotlin смотрели.
                0

                Вы давно не смотрели в kotlin. Давно же появились платформенные типы — String!, которые могут использоваться как nullable, как и non-null.

                  0
                  Извиняюсь, а это зачем? Чтобы нельзя было засунуть null в non-null? Но если он «как nullable, как и non-null», значит в него в любом случае можно сунуть null? Или это у них такой аналог аннотации @NonNull? Не знаю, по мне дак аннотации намного нагляднее, чем вспоминать что означает тут восклицательный знак, то ли указатель какой, то ли факториал вообще.
                    +1

                    Не, этот тип применяется только когда nullability неизвестна (когда дёргаем java код, на котором нет никаких аннотаций). Тогда компилятор позволит работать с этим типом как с nonnull, но везде натыкает проверок на null для early null propagation. Т.е. когда есть аннотации — то в Kotlin всё будет хорошо и везде будет нужный тип. Если нет (старый код или просто не заморачивались) — то можно не писать портянки из всяких ?., ?:,!!! и прочего и просто писать код, не столь безопасный (хотя если где-то прилетит null, где вы его не ожидали — то вылетит как можно раньше, а не когда этот null расползётся по всем структурам), но без манускриптов из символов и просто красивый.

                      0
                      А, ну понятно, то есть что-то на подобии принудительной проверки на null получается? Вообще проблема аннотаций в Java состоит в том, что они не обязательны, я бы вообще их отсутствие Fatal Error бы сделал, ибо помимо @NonNull существуют и @ Override, которые оооочень помогают, но которые тоже не обязательны, увы. В Котлине-то хоть они обязательны?) А-то ведь шило на мыло менять)
                        0

                        Ну nullability в kotlin на уровне типов, override не аннотация, а модификатор функции, его отсутствие — ошибка компиляции.


                        Посмотреть на разные особенности языка можно онлайн, например на https://play.kotlinlang.org/koans
                        Просто выбираете нужную тему и смотрите, как она реализована в данном случае.

                          0
                          Вот в том-то и дело, что в Java override это именно аннотация, причем не обязательная, даже не знаю почему произошло такое недоразумение. А за ссыль спасибо, то, что нужно, а-то все вокруг говорят, а чтобы пощупать — это проекты готовые нужны, ибо хеллоуворлда недостаточно.
                  +1
                  Не понимаю, почему его все так любят? Прям реально в комментах пишут какой Котлин классный. Как будто-бы до этого они ни на чем не писали и это их первый язык.
                    0

                    Как и со всеми языками — нужно пробовать, желательно не на хеллоуворлдах.
                    А вообще он решает много попоболи из Java, простой (относительно какой-нибудь Scala например), фичастый (корутины, инлайновые классы вон завозят потихоньку и т.п.), ну и с недавних пор мультиплатформенный (jvm, js, native).


                    Не помню кто сказал (кажется это был Андрей Бреслав), когда пишешь на Котлине — прям на душе приятно. Ты не пишешь очередные геттеры, а пишешь код.

                      0
                      Да это понятно, я и не спорю что Java весьма многословна, тут я отрицать не буду, и необходимость в более лаконичном языке назрела давно, просто можно было бы оставить человеко-понятный синтаксис, как например Ман для библиотеки корутин для Java, а не городить поверх Java свое. Такое ощущение что они специально творят такую дичь :/

                      Поэтому по мне все эти восторженные возгласы исключительно из-за появления современного и модного языка, который развивается и в котором уже есть почти все в отличии от других более старых ЯП, для которых многие библиотеки уже и вовсе не развиваются. Типа о, он даже лямбды из коробки поддерживает, вот это да! А корутины? Скоро будут? Вот это сервис, прям индивидуальный подход!) Я так это вижу…

                      P. S. Но по поводу геттеров и сеттеров посмотрю, спасибо, это проблема многих языков на самом деле и несколько изматывает при создании нового класса все это реализовывать вручную, а они это как реализовали?
                      0
                      Потому что в андроиде ооочень старая Java. А Котлин — хороший современный язык.
                        0
                        Ну я думаю язык надо выбирать в плане синтаксиса, а не новизны.
                          0
                          и андроидовская джава очень многословна и не отвечает требованиям к современным языкам.
                    +2
                    В D вроде бы используется arr[i] для индексирования с начала и arr[$-i] для индексирования с конца. То есть внутри контекста скобок доступна длина массива в виде символа $.
                      +1
                      А тут можно было бы ~0

                      ~0 вернёт обычный int. Это будет являться проблемой, т.к. усложнит индекацию и поломает существующий код, где индекс не является неотрицательным и начинается с нуля. Должен возвращаться объект типа Index, чтобы всё работало корректно.


                      ^0 как автор в статье написал, лучше бы для возведения в степень использовали.

                      Оператор ^ уже используется для операции XOR. Его нельзя использовать для возведения в степень. Так как унарного ^ не было, его решили использовать таким образом — всё в порядке.


                      А для возведения в степень разумным выглядит такая конструкция: a ** b.

                        0

                        С опретором ** тоже могут быть проблемы: Proposal: Exponentiation operator.

                          0

                          Ничего страшного. Указатели в C# — штука крайне редкоиспользуемая, чтобы отказываться от такого функционала.

                        0
                        Зачем для этого вообще отдельный термин?

                        Как минимум по бюрократическим причинам. В рамках C# 8.0 ведётся работа над некоторой подфичей Pattern Matching. Чтобы при разговорах о ней не размахивать руками в воздухе или не ссылаться на неё по номеру (Feature #5982), необходимо было придумать какое-то человеческое название. Конечному пользователю языка, если он на github.com/dotnet/csharplang не ходит и спецификации не читает, это название ненужно.

                          0

                          Но название фичи ведь должно кратко описывать её суть, а не просто из случайных слов состоять? А тут в чём рекурсивность? Тут скорее "Nested deconstruction"

                            0

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


                            Можно назвать nested, можно назвать recursive ― примерно одинаково.


                            var person = new Person
                            {
                                Name = new FullName
                                {
                                    First = "Vasya",
                                    Last = "Pupkin",
                                },
                                Age = 42,
                            };
                            
                            if (person is { Age: 42, Name: { First: var firstName, Last: "Pupkin" } })
                            {
                                Console.WriteLine(firstName);
                            }
                              0
                              А тут в чём рекурсивность?

                              Потому, что на вопрос "из чего может состоять паттерн?" среди можно ответить "паттерн" — т.е. он как бы определен через самого себя.

                                –1

                                Рекурсивность появится если паттерн будет состоять из самого себя, а не просто из набора других паттернов.


                                Если бы было так:


                                var person = new Person
                                {
                                    Name = new FullName
                                    {
                                        First = "Vasya",
                                        Last = "Pupkin",
                                        X = "secret",
                                    },
                                    Age = 42,
                                };
                                
                                if (person is { X: var x})
                                {
                                    Console.WriteLine(x);
                                }

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

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

                            странная идея о том, что стандарт языка должен строиться на том, что 5% его пользователей пользуются латехом, и, следовательно, он не должен латеху противоречить и должен быть похож. Особенно когда язык далеко не о математике (по сравнению с другими языками).
                            Ну и что ~0 что ^0 вообще интуитивно никак не намекают на то, что они делают. Лучше уж какой-то префикс типо reverse и т. д.
                          +8
                          По-моему либо тут, либо в предварительной документации Майкрософта что менее вероятно, некоторая путаница насчет индексов с конца. Сам не проверял, но вот что говорит Майкрософт, в предложении:
                          docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/ranges
                          и в описании новых фич:
                          docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#indices-and-ranges

                          The ^0 index is the same as sequence[sequence.Length]. Note that sequence[^0] does throw an exception, just as sequence[sequence.Length] does.

                          То есть ^0 это не последний элемент, как написано в статье, а индекс за-последним. Индексировать по нему нельзя, выбросится исключение! Это то же самое, что collection::end() в С++.

                          В Майкрософтовском предложении для взятия последнего элемента есть пример
                          var lastItem = list[^1]; // list[Index.CreateFromEnd(1)]

                          Разобравшись с индексами, становится понятно, что никакой путаницы с тем, включается ли последний элемент диапазона, нет — он никогда не включается! Независимо от того, берется ли последний индекс в диапазоне с начала или с конца.

                          [0..^0] работает и описывает весь массив именно потому что второй индекс ^0 не включается, иначе бы бросалось исключение.
                            0

                            Получается какой-то substr. 0..4 тут 4 элемента от нулевого. Да нет, зреет получается. 4..5 выберет один элемент? Они упрлс?

                              +5
                              Не, это как раз нормальное и ожидаемое поведение в С-подобных языках.
                              Вот если бы было как описал автор, то последнее значение в Range не включено, если индекс с начала, то включено, если индекс с конца — я бы решил что что-то не так.
                                –2

                                Что нормального в том, что в 4..5 помещается один элемент? Как это читать тогда вообще?

                                  +7

                                  Все нормально и очевидно: 5-4=1.

                                    +1
                                    Это действительно так, и это действительно некрасиво с точки зрения математики. Но понятно откуда это пошло — с С++овских итераторов begin() и end(), а там end() указывает за конец коллекции, а не на последний элемент, потому что иначе не сделать итерацию коллекций нулевой длины. Вот в Swift сделали оба вида диапазонов — и закрытые, и полузакрытые. Вполне логично и удобно, наличие обоих вариантов подчеркивает разницу между ними и уменьшает путаницу. Две точки для полузакрытых, три точки для закрытых. Можно даже мнемонически запомнить — три точки больше, значит и диапазон больше (включает последний элемент).
                                      +5
                                      Да уж. Змея сожрала свой хвост. Вообще-то этот вопрос уже исследовался много десятилетий назад. Тем самым Дейсткрой, который, как известно, не программист, потому что свои статьи не на компьютере набирал.

                                      Но, конечно, Apple не может же использовать опыт того самого Xerox PARC, с которого они слизали интерфейс MacOS (оригинальной версии, не MacOS X).

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

                                        интересно в каком языке все таки сделали полное математическое описание, с круглыми и квадратными скобками?

                                          +1
                                          Mesa. 1976й год. Собственно на опыт работы с Mesa обсуждение, ссылку на которое я давал выше, и ссылается.

                                          Вот всё как вы хотели: и все четыре варианта предусмотрены и да — таки круглые и квардартные скобки. Но «новое поколение» хочет, видимо, лично понаступать на те же грабли. Языки, созданные до их рождения, видимо «неправильные» — наверное за полвека что-то кардинально новое в математике случилось…
                                        0
                                        Нет, вы мне ответьте, как это читать? 2..7 — это «взять с третьего и не доходя до восьмого элемента»? В чём смысл такой записи? Ну то есть я вижу один смысл — когда у нас есть либо длина, либо «указатель на конец». Но мы берём срезы списков не только «до конца». Может быть, описание открытых интервалов само по себе и имеет смысл (хотя лично мне он неведом, это же целые числа в любом случае), но для срезов…
                                          +3
                                          В чём смысл такой записи?
                                          Вы в каждой строчке программы хотите увидеть «смысл жизни»? Или как?

                                          С полуинтервалами легко работать: их объединение — это тоже полуинтервал если они пересекаются — и это пересечение легко проверить, их разность — это один-два полуинтервала и там тоже всё легко считается. Самая важный прицип в программировании (разделяй и властвуй) с полуинтервалами делается тривиально, а с интервалами или отрезками — с трудом.

                                          И это всё совершенно не зависит от того — рассматриваете ли вы вещественные числа или целые.

                                          Может быть, описание открытых интервалов само по себе и имеет смысл (хотя лично мне он неведом, это же целые числа в любом случае), но для срезов…
                                          Вот именно для срезов полуинтрвалы и лучше. Потому что вы их делаете не для того, чтобы на доску повесить, а для того, чтобы какой-то алгоритм реализовать. И вот там +1/-1 встречается реже, если использовать полуинтервалы.

                                          Как уже упоминалось: попытки использовать другие варианты в истории были. Mesa вообще все четыре варианта поддерживала. Но оказалось, что это — хуже: если у вас в 70% случаев полуинтервалы, а в 30% случаев — что-то другое (да, иногда могут и отрезки и даже интервалы оказаться удобными), то в этих случаях возникает путаница уже из-за того, что человек забывает, что вот конкретно здесь у него не полуинтервал, а что-то другое. Лучше в этих [относительно редких] случаях +1/-1 написать…
                                            0
                                            Надо посмотреть завтра на свой код. Какие срезы и как я там использую. Пока что мне кажется, что я всегда знаю номер конечного элемента, а не следующего за ним.
                                            Но вы так убедительно говорите, что может быть, я просто привык.
                                              0
                                              Вот только что работал со срезами. Разделить массив на две части по накопленной сумме. То есть я ищу номер первого элемента (назовём его limit), при котором сумма станет не меньше установленного значения, а затем разделяю массив на две части — к этому элементу и после него. Получается, с этим синтаксисом мне надо будет писать два раза limit+1 — для первой части, потому что интервал полуоткрытый справа, и для второй части — потому что вторая часть начинается после limit.
                                              Вы как хотите, но мне это по-прежнему кажется некрасивым. Тогда уж вводить синтаксис для закрытой и открытой границы — причём любой, правой или левой. Тогда было бы удобно — я писал бы что-то вроде [0..limit] и (limit… array.size()).
                                                +2

                                                Если limit оказался за правым краем (limit == count), то [0..limit] вылетит без видимых причин. "Красивый код" обезобразится некрасивый проверкой, которая будет торчать посреди красивого кода камнем посреди пустыни. А код с явным добавлением единицы органично требует такой проверки, явно указывает на её необходимость, так что вы про неё, во-первых, не забудете, а, во-вторых, не будете плеваться, натыкаясь на неё глазами потом.

                                                  0
                                                  Факт достижимости нужного результата на списке нужно в любом случае проверять так или иначе. Независимо от использования срезов.
                                                  И я, честно сказать, не понял, чем тут помогает ±1 в десяти местах.
                                              +5
                                              В чём смысл такой записи?

                                              1. Попробуйте взять пустой интервал если запись a..b эквивалентна отрезку [a,b], а не полуинтервалу [a,b). Отрезок [a,a] непустой, он состоит из 1 элемента a. Писать a..a-1, что ли? Или a+1..a? Для полуинтервалов же это не проблема, [a,a) — пустой.
                                              2. Попробуйте взять два смежных отрезка. [a,b] и [b,c] не подходят, так как элемент b входит в оба отрезка. Писать a..b-1 и b..c? Или a..b и b+1..c? Для полуинтервалов же это не проблема, [a,b) и [b,c) — смежные и не пересекаются, их суммой самым натуральным образом оказывается [a,c).
                                                0
                                                Я думаю что претензии к тому, что a..b выглядит симметричной при том, что a входит в интервал, а b — не входит. Что, действительно, выглядит немного странно: конструкция вроде симметрична, а на самом деле — нет.

                                                Но это уже чисто вопрос привычки: получинтервалы реально удобнее, а что немного непривычно выглядят — так тут уж «как получилось».
                                                  0

                                                  Возможно так. Хотя взять ту же пару std::begin(), std::end() — вроде выглядит симметрично, на на деле нет. Тех, кто интервалов касался, эта кажущаяся симметрия вряд ли введёт в заблуждение.

                                                    +1

                                                    Как тут, симметрично?


                                                    for (int i = a; i < b; i++)
                                                      0
                                                      Так там и выглядит выражение несимметрично, почему должна быть симметрия в функциональности?
                                                    0

                                                    Так это не просто сферический интервалы в вакууме. Это срезы. Вам часто нужно брать пустой срез?
                                                    К интервалам как таковым претензий нет, end() Это действительно удобно.

                                                      0
                                                      Вам часто нужно брать пустой срез?

                                                      Да, например, при парсинге строки часто вылазят подстроки нулевой длины.

                                                        0

                                                        В чём принципиальная разница, не улавливаю? Что срез, что интервал — это просто функция, отражающая пары чисел (границы) на множества подпоследовательностей (интервалы). Если я пишу a..b, то мне нужно, чтобы множество всех возможных подпоследовательностей включало в себя и пустые, для полноты и общности.

                                        • UFO just landed and posted this here
                                            +6

                                            Нормальный функциональный код.

                                            • UFO just landed and posted this here
                                                +6

                                                Стареете, хуже воспринимаете нововведения — это нормально.

                                                • UFO just landed and posted this here
                                                    0
                                                    Например Реймонд Ваган Дамадьян, изобретатель МРТ, ему сейчас около 80 лет.
                                                    И что он такого сделал «прорывного» в 5 лет? Когда он свою первую статью про МРТ публиковал — ему 35 было. А IT… В основном сейчас на Big Data молются и прочие всякие Map Reduce. Джеффу Дину, когда он это всё придумал — было 32. И это — в общем типичная история: случаи, когда «седовласые дядьки за 50» что-то реально изобретают (а не приписывают себе что-то, что сделали другие) — случаются, но их очень-очень мало.

                                                    Именно они стоят у руля лабораторий, которые дают вам технологие, на которые вы молитесь.
                                                    Стоять у руля != изобретать. Многие изобретения вообще случаются потому, что учёные, которые их делают всё проводят «мимо» руководителей.

                                                    И это конечно факт прискорбный, что «истина недоступна большинству», но ожидаемо — так что по барабану.
                                                    Как раз большинству она доступна. А вам — похоже, что нет.
                                                    • UFO just landed and posted this here
                                                        0
                                                        А сколько Торвальдсу когда линукс стал мейнстримом и реально вошел в жизнь каждого?
                                                        А причём тут это вообще? Торвальдс уже давно код сам не пишет и, фактически, играет роль того самого «заведующего лабораторией». Под руководством которого другие люди занимаются инновациями. Последняя действительно прорывная вещь, которую он сделал — это был Git. Что случилось больше 10 лет назад и было ему тогда, оп-па, таки 36.

                                                        И я привел парочку примеров в подтверждение (помните чего? именно неверности «Стареете, хуже воспринимаете нововведения»).
                                                        Это где, извините? Всё что вы привели — несколько примеров того, как человек в 20-30-40 лет что-то сделал — что захватило мир гораздо позже. И те же самые люди, которые в молодости «переворачивали мир» — в старости начинают, зачастую, выглядеть чудаками. Как тот же Столлман, который реально очень сильно изменил путь всей индустрии когда-то… а сегодня отказывается пользоваться браузером, чем всех умиляет.

                                                        Потому что он утверждает, что знает, что именно определяет мое мнение.
                                                        Нет, он просто шутит. А вот уже то, что вы безобидные шутки воспринимаете «в штыки» — заставляет хлопнуть себя ладонью по лбу и громко расхохотаться.

                                                        И Аристотель, и многие ученые мужи современности, и собственно лично я, считаем именно так. Вы конечно можете думать как вам вздумается. Но на этой двойственности мнений мы и закончим с этим пунктом.
                                                        Ну, Аристотель — он, конечно, дядька умный, да. Но вот вы ответьте на один вопрос: если вам нужно будет количество лап у мухи узнать — вы просто возьмёте и посчитаете или будете тоже схоластику разводить?
                                                        • UFO just landed and posted this here
                                                  0
                                                  Но это же не означает, что подобный код перестает быть Г.

                                                  Нет, не означает, но он и в самом деле перестаёт быть этим самым Г.


                                                  Я называю это "нулевой рефакторинг": нулевой рефакторинг — это частный случай рефакторинга кода, при котором код — как текст — не изменяется, но при этом (иногда кардинально) меняется его архитектура, архитектура программы. Нулевой рефакторинг может быть итоговым результатом настоящего сложного многостадийного рефакторинга когда в итоге неожиданно оказывается, что получился "полный круг", а может совершаться исключительно в голове программиста в результате повышения его кругозора и опыта работы.


                                                  Вот чтобы обсуждаемый код перестал быть "Г.", требуется именно нулевой рефакторинг, и большое количество людей в отрасли его уже проделали.

                                                  +2
                                                  Дело не в том, функциональный он или нет, а в том, что он абсолютно нечитаем, его будет тяжело сопровождать.
                                                    +7

                                                    А как вы определили, что он "абсолютно не читаем", а не просто "непривычен"?

                                                    • UFO just landed and posted this here
                                                        –3
                                                        — этот код легко читается
                                                        С чего вы взяли? Кто вам сказал что он легко понимается всей командой разработчиков?

                                                        Так какая выгода от такого матчинга? Один вред.
                                                        Реализация функционального подхода наверное.

                                                        — не нужно набирать тонну говноскобок
                                                        Нужно набирать тонну говно-if'ов

                                                        — есть возможность использовать разные типы исключений
                                                        Кто вам запрещает вместо строки возвращать разные исключения?

                                                        легко проверяется во время дебага (даже точки останова ставятся элементарно)
                                                        Unit test, не слышали?

                                                        Внезапно у вас поменялась структура объекта, который надо валидировать, и вы радостно переписываете все свои if'ы.
                                                        • UFO just landed and posted this here
                                                            –4
                                                            но обсуждаем то, что в статье,
                                                            Да и что вы будете делать, если...
                                                            Как мне кажется это взаимно исключающие вещи.
                                                            если вам так будет легче, то в рассматрвиаемом примере кода, реализация функционального подхода — вред для читаемости и сопровождения.
                                                            Не вы вызывает затруднений у меня. И как бы это же синтетический пример, вы помните?
                                                            А что такое читаемость я сейчас расскажу:
                                                            Серьезно?
                                                            В своей работе, разработка приборов учета и ПП безопасности, несколько лет успешно используем ДРАКОН. Для нас код вторичен.
                                                            там словесный понос.
                                                            В вашей комнате разработчиков это допустимая словесная форма?
                                                              0
                                                              Все что универсально — проще для восприятия и понимания.

                                                              Чем меньше универсальности, тем проще понимать. Чем более ограниченным инструментом вы пользуетесь, тем меньше вариантов семантики кода.


                                                              Паттерны чуть более ограничены, чем ифы, поэтому то, что по смыслу является паттерн-матчингом, проще, лучше и понятнее делать на соответствующем синтаксисе.

                                                            +1
                                                            этот код легко читается

                                                            Почему вы считаете что он читатся легко? Рациональные аргументы?


                                                            if (person.PhoneNumber == null)
                                                            throw new ArgumentException("Phone number missing entirely");


                                                            if (person.PhoneNumber.Number == 0)
                                                            throw new ArgumentException("Actual number missing");


                                                            if (person.PhoneNumber.Code < 0)
                                                            throw new ArgumentException("WTF?");


                                                            Лично мне не очень нравится три раза писать и читать одно и то же.


                                                            — не нужно набирать тонну говноскобок

                                                            Круглые не считаются?


                                                            модификация кода валидации на что-то более сложное не требует изменений всего куска кода

                                                            Это также аргумент чтобы заменить a * 2 на switch(a){ case 1: return 2; case 2: return 4

                                                              +2

                                                              Расскажу как для меня видятся эти два кусочка кода, возможно проследив за моим ходом мыслей вы поймете, что бывают люди, думающие по другому, и это тоже правильно (а может быть и более правильно):


                                                              1. Код с матчингом читается легче, потому что все ветки находятся на соседних строчках, друг под другом, и их легко визуально сравнивать. Тот самый ваш пример про два хеша — тут то же самое. Код с if — разделен блоками кода — это в некоторой степени засоряет визуальное восприятие. Однако главное не это.
                                                                Код с шаблонов описывает сам себя — он декларативен, он как две картинки с отличиями, которые дети сравнивают в детском саду. Это просто как дышать. Код же с if — императивен. Мне надо загрузить строчку сравнения в "оперативную память", чтобы понять что именно там сравнивается. А чтобы сравнить два условия — мне вообще необходимо нагрузить свой мозг. Сравните это с "в верхней строчке после двоеточия null, а в нижних — что-то… все понятно".


                                                              2. При дебаге кода с сопоставлением шаблона, я ставлю одну точку останова — в месте инициализации person, затем вывожу его визуальное представление и визуально сравниваю с шаблоном. Один раз делаю операцию из детского сада. Я точно уверен, что внутри кейса person никак не изменится — мне вообще не нужно жать step forward, потому что это всего один шаг.
                                                                Сравните с кодом с if, где в принципе объект person может поменяться по дороге (по крайней мере теоретически). Кроме того, я скорее всего буду жать step forward 4 раза, и сравнивать конкретные вещи 4 раза — в первом случае это person с null, потом PhoneNumber с null и так далее. По-моему, когнитивная нагрузка и временные затраты в этом случае больше.


                                                              3. Набирать много скобок или много if — это в принципе дело вкуса, и врядли аргумент. Если бы это было важно для вас, вы бы программировали на Питоне. А тут всетки не Лисп, чтобы не это ругаться. Выглядит как обычный json.


                                                              4. Очевидно, что вместо строк объекту person можно было при присвоить эти самые разные исключения, чтобы потом их зарейзить:


                                                                { PhoneNumber: null } => new ArgumentException("Phone number missing entirely"),
                                                                ...
                                                                throw error;

                                                                Вот только в этом коде и Вашем есть большая разница: Ваш код нарушает базовые принципы структурного программирования. Наличие более одной точки выхода из блока кода значительно увеличивает когнитивную нагрузку (надо держать в голове где там оно могло прервать поток исполнения) и приводит к багам при дальнейшем изменении этого участка кода. 4 throw это 4 goto по своей сути. Возможно, программисты C# привыкли к такому, и это наоборот best-practise, но я предпочитаю в этом вопросе следовать Дейкстре неукоснительно.


                                                              5. По-моему все ровно наоборот. Предлагаю вам не изменяя целиком ваш блок, добится того же, что и я, при добавлении одной строки:


                                                                var error = person switch {
                                                                  null => "Object missing",
                                                                  { PhoneNumber: null } => "Phone number missing entirely",
                                                                + { PhoneNumber: { Number: 0, Code: var code} } when code < 0 => null,
                                                                  { PhoneNumber: { Number: 0 } } => "Actual number missing",
                                                                  { PhoneNumber: { Code: var code } } when code < 0 => "WTF?",
                                                                  { } => null // no error
                                                                };
                                                                if (error != null)
                                                                  throw new ArgumentException(error);

                                                                Пример синтетический — однако можно представить появление валидации такого типа (когда по отдельности два условия — ошибка, а вместе — нет). Скорее всего в этом месте в вашем решение появится nested if, а это очевидно увеличивает сложность.


                                                              6. Место — по всей видимости так-же не аргумент.



                                                              Подводя итог, я ни в коем случае не собираюсь навязывать свою точку зрения, однако призываю вас взглянуть на вопрос пошире — в том числе с моей точки зрения, и не быть настолько категоричным.

                                                                +2
                                                                Нет он именно не читаем!
                                                                Нет, он прекрасно читаем. Ваша неспособность легко разобраться с этим кодом не означает, что «насколько некачественный код продуцирует большая часть разработчиков».
                                                                — не нужно набирать тонну говноскобок
                                                                В Вашем коде 18 скобок.
                                                                  +2

                                                                  Я заметил на своём опыте, а также косвенно на опыте коллег, что любой непривычный стиль написания кода кажется объективно нечитаемым и вызывает отторжение. Такое когнитивное искажение. Если некоторое время попытаться им пользоваться, несмотря на негативное отношение, искажение пропадает и можно пытаться проводить более объективное сравнение.

                                                                  0

                                                                  Во-первых, pattern matching может проверяться тайпчекером на полноту, а последовательность ifов проверять на полноту сложнее.


                                                                  Во-вторых, пример из статьи скучноват. Давайте мы лучше возьмём, например, красно-чёрные деревья


                                                                  data Tree a
                                                                    = Empty
                                                                    | Node
                                                                      { color :: Color
                                                                      , left :: Tree a
                                                                      , elt :: a
                                                                      , right :: Tree a
                                                                      }

                                                                  и функцию балансировки для случая вставки в левую подветвь:


                                                                  lbalance :: Color -> Tree a -> a -> Tree a -> Tree a
                                                                  lbalance B (Node R (Node R t1 e1 t2) e2 t3) e3 t4 = Node R (Node B t1 e1 t2) e2 (Node B t3 e3 t4)
                                                                  lbalance B (Node R t1 e1 (Node R t2 e2 t3)) e3 t4 = Node R (Node B t1 e1 t2) e2 (Node B t3 e3 t4)
                                                                  lbalance color left elt right = Node { .. }

                                                                  Как будет выглядеть код без паттерн-матчинга, с ифами и прочим?


                                                                  Ещё бы эффектнее это выглядело, если бы в х-ле был сахар для случая, когда правые части для разных паттернов выглядят синтаксически одинаково, но тут х-ль, увы, сливает некоторым ML'ам. Запилить, что ли...

                                                            0

                                                            Скорее всего вам в качестве внеклассного чтения следует ознакомиться с понятиями функционального программирования.
                                                            Если у вас C# основной — тогда советую crash course F#.


                                                            Судя по тому, что в мире происходит, видны явные движения индустрии в этом направлении.

                                                              0
                                                              Не вижу почему такие тренды и «явные движения» должны приводить к смешению двух языков в один. Если у вас есть лучшие в мире пылесос и телевизор, разве это автоматом значит, что пылесосу к месту придется 40" экран, а телевизору — мешок с мусором?
                                                                +1

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


                                                                Сможет ли C# сообщество пойти по этому пути — я не знаю. Скорее всего сможет, потому что процесс начался ещё в .net 3.5 с появление linq, в отличие от Java мира, где было легче седлать Scala чем как-то трогать саму Java.


                                                                Так что теперь вопрос только за «программистами» — будут ли они достаточно гибкими.

                                                                  +2
                                                                  В одном проекте отлично могут сосуществовать .cs, .fs и .il файлы и/или библиотеки. Лично мне кажется, что пытаться слепить их все в одну сущность — откровенно неудачная идея. Время покажет, конечно.
                                                                  0
                                                                  Вы сравниваете изначально разные вещи, с различным предназначением. Для ЯП нормально взаимствовать друг у друга.
                                                                +2

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

                                                                  +2
                                                                  Хотя есть один момент, что расстраивает:
                                                                  У дизайнеров было два выбора: либо молча сглатывать неохват паттерна делая default-init (иначе говоря, возвращать default(T) из свича у которого не замэтчился ни один паттерн) или же бросать исключение. Микрософт выбрали второе...
                                                                  Во многих ФП языках ветка по умолчанию обязательна, и это тот третий и самый, на мой взгляд, логичный вариант.
                                                                    0

                                                                    Это в каких?


                                                                    В хаскеле не надо, там неплохой эвристический тоталити чекер. В идрисе и агде не надо, там по построению тотальность проверяется. В известных мне диалектах ML'а тоже не надо.

                                                                      0
                                                                      Вообще согласен — такого действительно нет. Хаскель, кстати, тоже ошибку кидает Non-exhaustive patterns in case.

                                                                      И в принципе понятно почему нет, если мы перебрали все возможные варианты (например, bool: true|false), то дефолтная ветка не нужна, соответственно нельзя ее сделать обязательной на уровне языка.

                                                                      Но это проблему можно (и на мой взгляд нужно) решить через предупреждение компилятора.
                                                                        0

                                                                        Но по очевидным причинам в том же хаскеле у вас всегда будут либо false positives, либо false negatives.


                                                                        В Idris тоже, например, будут, для некоторого множества функций.

                                                                        0
                                                                        там неплохой эвристический тоталити чекер

                                                                        Он там был неплохой, а в какой-то версии его зачем-то переписали на прямой тупой перебор всех вариантов, и он стал очень тормозной.

                                                                  +4
                                                                  чем выше версия C# тем ужаснее становится синтаксис… хорошо хоть даже сами microsoft большинство этого ужаса не используют в примерах
                                                                    –1
                                                                    Это ответ автору статьи

                                                                    Все-таки с неймингом у МС получается ооочень плохо, прям фееричный булшит. Сначала nullable reference types — масло масляное, ибо референсные типы являются nullable по определению

                                                                    Серьёзно? Проблемы у Вас. По какому такому «определению»? Вот определение ссылочного типа в C#

                                                                    В C# существуют две разновидности типов: ссылочные типы и типы значений. В переменных ссылочных типов хранятся ссылки на их данные (объекты), а переменные типа значений содержат свои данные непосредственно. Две переменные ссылочного типа могут ссылаться на один и тот же объект, поэтому операции над одной переменной могут затрагивать объект, на который ссылается другая переменная.

                                                                    Где здесь упоминание о том, ссылочные типы обязаны быть nullable? Какая вообще связь между размещением объекта в куче и (не)возможностью объвлять этот тип nullable?
                                                                      +2
                                                                      Возможность размещать объекты в куче — означает nullable.
                                                                      Создаем ссылочную переменую и забываем проинициализировать. Что там будет храниться? Т.к. это ссылка, значит null, потому что дефолтное значение ссылки — null.
                                                                      Делаем вывод, что
                                                                      референсные типы являются nullable по определению
                                                                        0
                                                                        Создаем ссылочную переменую и забываем проинициализировать.
                                                                        Хммм. Ok.

                                                                        Что там будет храниться?
                                                                        Ничего, потому что программа не скомрилируется и не запустится — чем такой ответ был бы плох?

                                                                        Т.к. это ссылка, значит null, потому что дефолтное значение ссылки — null.
                                                                        Делаем вывод, что
                                                                        А можно «мусок», как в C оставить. Или ссылку на какой-нибудь специальный синглтон.
                                                                          0
                                                                          Создаем ссылочную переменую и забываем проинициализировать. Что там будет храниться?


                                                                          Есть языки в которых Вы не сможете забыть проинициализировать переменную ссылочного типа, если она явно не обявлена как nullable: Scala, Kotlin, TypeScript и C# 8 как пример, ваш код просто не скомпилируется.

                                                                            +4

                                                                            На самом деле в C# нельзя забыть проинициализировать переменную даже если она nullable.

                                                                              0
                                                                              Нельзя, но если прочитать C#-спецификацию, то можно.
                                                                              C# defines seven categories of variables: static variables, instance variables, array elements, value parameters, reference parameters, output parameters, and local variables.

                                                                              Пруфлинк

                                                                              Вы же не станете возражать, что забыть проинициализировать, например, instance variable можно? И при этом она будет автоматичестки инициализирована default-значением?
                                                                      +2
                                                                      Помню, в ранних черновиках для “Nullable Reference Types” было совершенно здравое предложение просто добавить символ “!” ко всем полям, переменным и тд. которые не могут быть null. По сути, это был бы аналог атрибута [NotNull]. Это решало бы проблему с null ref там, где ее надо решить и не ломало бы обратной совместимости.
                                                                        +2
                                                                        Оно не совсем здравое

                                                                        1. В подавляющем большинстве случаев (> 90%) nullabe типы не нужны. Это означет, что все было бы утыкано воклицателными знаками, как будто программа кричит на программиста.

                                                                        2. Это привело бы к тому, что программисы просто ленились бы писать (!) и делали бы все типы nullable, что способствовало бы плохим практикам

                                                                        Текущее решение было тщательно продумано и обсуждено с общественностью.
                                                                          +3
                                                                          Я согласен, что вариант с "!" не идеален как и любое компромиссное решение, но им хотя бы можно было безболезненно начать пользоваться постепенно обновляя существующую кодовую базу.

                                                                          Сейчас же, многие просто не будут включать эту опцию в уже существующих проектах потому как понять, где нужно ставить “?”, а где не нужно, зачастую очень сложно.
                                                                            0
                                                                            где нужно ставить “?”, а где не нужно, зачастую очень сложно.

                                                                            как это так? объясните. имхо, это же очень просто

                                                                              +1
                                                                              Вот у вас есть существующий класс
                                                                              class MyClass
                                                                              {
                                                                                 public string Id;
                                                                                 public SomeType1 Property1;
                                                                                 public SomeType2 Property2;
                                                                                 ...
                                                                                 public SomeTypeN PropertyN;
                                                                              }
                                                                              


                                                                              Глядя на этот класс я с большой долей вероятности могу сказать какое поле, скорее всего является, обязательным.

                                                                              Какие поля являются «не обязательными» нужно выяснять разбирая код программы.

                                                                              Теперь я включаю эту новую опцию, и получаю кучу предупреждений при попытке собрать проект. Дальше есть 4 варианта:
                                                                              1) Забить на предупреждения (тогда зачем это все?)
                                                                              2) Пометить все поля как "?" (тогда зачем это все?)
                                                                              3) Отключить эту опцию (тогда зачем это все?)
                                                                              4) Потрать кучу времени на рефакторинг работающей и отлаженной программы (здорово конечно, но надолго ли вас хватит).
                                                                                0

                                                                                это вопрос про настройку большого старого проекта. да, здесь единственный 100% точный вариант — это просмотреть код, чтобы знать, что nullable, а что нет. (хотя по хорошему уже бы иметь какие-то маркеры, комментарии там, или атрибуты, чтобы декоративно было все видно).
                                                                                но если вы не хотите тратить время на подобную модернизацию большого и старого проекта, тогда зачем вы написали этот коммент?

                                                                                  0

                                                                                  Эта ветка начиналась с напоминания о том, что были и другие варианты как реализовать nullable ref types.
                                                                                  Идея помечтать обязательные поля "!" имеет обратную совместимость и не требует костылей в виде опций компилятора.

                                                                                    0

                                                                                    но если выбирать между старыми проектами и новыми, то выбор очевиден

                                                                                  –1
                                                                                  «Детей, конечно, нельзя убивать. Но что-то с ними надо делать». Даниил Хармс.

                                                                                  Вы же понимаете, что «Ошибку на миллиард долларов» так просто не исправишь? Процесс будет долгим и сложным. Если включить nullable контекст во всем проекте, то программиста завалит предупреждениями, это уже обсуждалось неоднократно. К счастью, контекст можно включать для отдельных участков кода с помощью директив, это позволит постепенно перевести проект на новые рельсы, путем постепенного увеличения кода в non-nullable контексте. Всё учтено могучим ураганом.
                                                                                  0
                                                                                  Здесь проблема, что теперь над каждым полем, свойством, параметром надо дополнительно думать, может ли оно потенциально быть null. Раньше мы думали только об Nullable значимых типах, а теперь нужно вообще обо всех типах. Причем, эта настройка только проверяет, что вы сами в вашем коде не можете присвоить null, но есть огромное число фреймворков (тот же MVC), где объекты создаются им самим из, например, http запроса. Что должно быть, если свойство не null, но в запросе его нет — exception или все-таки значение null? Сейчас это будет null. Т.е. потенциально возможно, что даже nonnullable reference type все равно будет равен null. И теперь в каждом месте программы я должен об этом думать…
                                                                                    0

                                                                                    но позвольте! а как вы раньше писали код, серьезно не задумываясь о том, что, в какой то конкретный момент, вот в этом вот месте, вы планируете возвращать null, и что вызывающий код должен сделать соответсвующую проверку? или вы просто по дефолту везде ThrowIfNull писали?


                                                                                    а что же касается "огромного количество фреймворков", то здесь ожидается полезность фунции только если конкретный фреймворк использовал это нововведение. скорее всего, MS введет атрибут [DontCheckForNullables] для референсов разных DLL.

                                                                                      +3
                                                                                      Задумываться над чем, простите? Может ли объект быть null? Может, т.к. это reference type. А вот сейчас мы будем смотреть, есть ли у него «знак вопроса» или нет. Но самое ужасное пока в этом новшестве то, что runtime не гарантирует, что если вы объявите свой тип без «знака вопроса», то в нем не будет null.
                                                                                      Т.е. вот у нас функция принимает строковый параметр (без ?). Нужно ли проверять его на null? В текущей парадигме — да, в новой — нет. Хотя null потенциально может быть и там и там.
                                                                                      Текущее решение — не доделано, это костыль, чтобы улучшить проверки кода на потенциальные NullReferenceException. Но оно может привести к тому, что многие начинающие или низкоквалифицированные разработчики (коих хватает) будут считать, что в таких типах null не может быть никогда и это может привести не к уменьшению, а к увеличению этих самых NullReferenceException-ов, которые как известно, мы получаем в самый неожиданный и непредсказуемый момент (иначе бы мы их выловили бы еще на стадии тестирования).
                                                                                0

                                                                                Плюс '?' и так уже используется для nullable value types: int vs int?

                                                                                  +2
                                                                                  Для Value Types, "?" — это синтаксический сахар для структуры Nullable<>:

                                                                                  int? = Nullable<int>

                                                                                  Для Reference Types, "?" — это просто подсказка компилятору + добавляется [NullableAttribute] (подсказка компилятору при испльзовании кода из других сборок)

                                                                                  Разница ещё в том, что в Value Types "?" логически относится к типу, а в случае с Refernce Types к переменной, полю или методу, так, что с точки зрения логики, надо было бы писать:

                                                                                  string myVariable? = null

                                                                                  a не

                                                                                  string? myVariable = null
                                                                                    –1

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

                                                                                      +1
                                                                                      Не совсем понял про анонимный тип: в примере тип переменной это string и он никакой не анонимный.
                                                                                        –1

                                                                                        Нет, тип второй переменной — string?

                                                                                          –1
                                                                                          string? это не анонимный тип, это обычный string.

                                                                                          Под анонимными типами в C# понимается совершенно другая вещь, например:
                                                                                          var myVariable = new {Field1 = "Field 1", Field2 = 2};
                                                                                          

                                                                                          увидев такой код, компилятор создаст тип:
                                                                                              class ___AnonymusXXXX
                                                                                              {
                                                                                                  public string Field1 {get; private set;}
                                                                                                  public int Field2 {get; private set;}
                                                                                              }
                                                                                          

                                                                                            +1

                                                                                            Тут произошел конфликт названий, речь шла о другом виде типов. Статические типы (т.е. типы, известные компилятору) вовсе не обязаны совпадать с рантайм-типами.


                                                                                            Некоторые типы существуют только в исходном коде, а в рантайме полностью заменяются другими. Их-то ApeCoder и не вполне корректно обозвал "анонимными".


                                                                                            В языке C# уже были примеры таких типов — это dynamic, заменяемый на object; и кортежи, заменяемые на ValueTuple с потерей информации об именах свойств. Теперь появился ещё один класс таких типов, только и всего. Но типами они от этого быть не перестали.

                                                                                              +2
                                                                                              Вот оригинальный текст:
                                                                                              Теперь самый важный вопрос: что же поменялось в IL? С точки зрения исполняемого кода — ничего! Но с точки зрения метаданных поменялось конечно: теперь в типе который использует nullable аннотации все типы которые являются nullable проаннотированы атрибутом [Nullable]. Сделано это по понятным причинам: чтобы потребители вашего кода могли использовать ваши аннотации.


                                                                                              Этот string существует и в исходном коде и в рантайме.
                                                                                              По сути запись:
                                                                                              public string? MyProperty { get; set; } = null;

                                                                                              это синтаксический сахар для:
                                                                                              [System.Runtime.CompilerServices.NullableAttribute]
                                                                                              public string MyProperty { get; set; } = null;


                                                                                              Кстати, по поводу:
                                                                                              … заменяемые на ValueTuple с потерей информации об именах свойств

                                                                                              имена свойств не теряются, они сохраняются в метаданных.
                                                                                                –2

                                                                                                Каким образом написанное вами опровергает тот факт, что string? — тип?


                                                                                                имена свойств не теряются, они сохраняются в метаданных

                                                                                                Они пропадают из информации о типе. Был тип (foo: int, bar: string), стал ValueTuple<int, string>

                                                                                                  +1
                                                                                                  string? это тот же тип string, между ними нет разницы

                                                                                                  но int? это действительно другой тип — Nullable.

                                                                                                  Это как раз та путаница, о которой я говорил в предыдущих комментариях.
                                                                                                    0
                                                                                                    string? это тот же тип string, между ними нет разницы

                                                                                                    Вы ходите по кругу. Почему между ними нет разницы, если для них определены разные допустимые операции (второму нельзя присвоить null)?

                                                                                                      0
                                                                                                      второму нельзя присвоить null

                                                                                                      Очень даже можно, просто компилятор выдаст предупреждение, что эта переменная помечена как «Not Null», а вы пытаетесь присвоить эй null.
                                                                                                      Сам тип string символ "?" никак не меняет.

                                                                                                      Более того, если я попытаюсь использовать сборки скомпилированные в C#8 из C#7, то все поля помеченные как «string?» будут видны как обычные «string» и накаких предупреждений (или ошибок компиляции) не будет, если я попытаюсь присвоить им null.

                                                                                                        –1

                                                                                                        А всегда ли это будет предупреждением, или это просто текущее состояние компилятора?


                                                                                                        Более того, если я попытаюсь использовать сборки скомпилированные в C#8 из C#7

                                                                                                        Естественно, что в разных языках разные системы типов.

                                                                                                          0
                                                                                                          Сам тип string символ "?" никак не меняет.

                                                                                                          "Тип данных (тип) — множество значений и операций на этих значениях."


                                                                                                          Так как набор операций изменяется, то это уже другой тип.


                                                                                                          эта штука не меняет тип String, но она обозначает тип "nullable string" который может быть проассоциирован с переменной. Так же как и int[10] не меняет тип int, а делает новый тип по int и количеству элементов.


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

                                                                                                            0
                                                                                                            Если бы «string?» был другим типом, то требовалось бы приведение типов, но код ниже будет скомпилирован без предупреждений:

                                                                                                            string notNullable = "value1";
                                                                                                            string? nullable = "value2";
                                                                                                            if(nullable != null)
                                                                                                            {
                                                                                                                  notNullable = nullable;
                                                                                                            }


                                                                                                            но зато:

                                                                                                            int? nullable = 1;
                                                                                                            int notNullable = 2;
                                                                                                            
                                                                                                            if (nullable != null)
                                                                                                            {
                                                                                                                notNullable = nullable;
                                                                                                            }


                                                                                                            выдаст ошибку:
                                                                                                            error CS0266: Cannot implicitly convert type 'int?' to 'int'. An explicit conversion exists (are you missing a cast?)
                                                                                                              +1

                                                                                                              Вы так пишете, как будто смарт-касты в других языках ещё не изобрели...

                                                                                                                +1
                                                                                                                Если бы «string?» был другим типом, то требовалось бы приведение типов

                                                                                                                Ну, не факт, это зависит от языка, скажем, в F# нельзя неявно привести Int32 к Double, а в С# можно.

                                                                                                                А что рефлекшн говорит по поводу «string?»?
                                                                                                                  +2
                                                                                                                  А что рефлекшн говорит по поводу «string?»?

                                                                                                                  Что это обычный string. А атрибут относится не к типу, а к переменной.

                                                                                                                    –1
                                                                                                                    По поводу Int32 и Double — это честное неявное приведение типов, которое не зависит от каких-либо предварительных проверок.

                                                                                                                    Я такое приведение могу сделать и для других типов определяя оператор «implicit».

                                                                                                                    А что рефлекшн говорит по поводу «string?»?

                                                                                                                    Ответ в статье:
                                                                                                                    Теперь самый важный вопрос: что же поменялось в IL? С точки зрения исполняемого кода — ничего!
                                                                                                                      +1
                                                                                                                      Статические типы (т.е. типы, известные компилятору) вовсе не обязаны совпадать с рантайм-типами.
                                                                                            –1

                                                                                            Есть готовый синтаксис:


                                                                                            int i1 = 10;
                                                                                            int? i2 = null;

                                                                                            ну и нечего изобретать велосипед:


                                                                                            string s1 = "10";
                                                                                            string? s2 = null;

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


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

                                                                                      +2
                                                                                      Поэтому он теоретически может быть отрицательным, хотя конечно в C# запись x[-1] лишена всякого смысла.

                                                                                      Не лишена.
                                                                                      Во-первых — можно перегрузить индексатор в вашем классе и корректно обрабатывать в нем отрицательные индексы.
                                                                                      Во-вторых, в C# массивы могут индексироваться не только с 0:
                                                                                      var x = (int[,])Array.CreateInstance(typeof(int), new[] { 42, 42 }, new[] { -1, 2 });
                                                                                      x[-1,3] = 1234;
                                                                                      
                                                                                        –5

                                                                                        надеюсь они уберут этот бесполезный функционал

                                                                                        0
                                                                                        А будет ли в c# аналог lateinit того же Котлина?
                                                                                          0
                                                                                          Ну пока не планируется. По сути, если вы инжектите свойство через DI, вам придется приписать к типу вопросительный знак.
                                                                                            0
                                                                                            Другое. Запрос к api возвращает json, который десериализуем в граф объектов. И хоть мы знаем, что у нас после этого будут инициализированы все свойства, приходится определять все поля nullable и потом в тысяче мест добавлять!.. Или же инициализировать свойства значениями, что в случае вложенных классов может быть очень развесисто и попросту захламляют кучу, ведь сразу после создания объекта все эти свойства будут перезаписаны.

                                                                                            А хотелось бы сказать компилятору «да я уверен, что там null не будет, не заставляй меня их инициализировать»
                                                                                              0

                                                                                              Для этого всего-то нужно инициализировать свойства в конструкторе...

                                                                                                0
                                                                                                А в чем отличие? Ну кроме места где будет инициализация? Результат тот же.
                                                                                                  0

                                                                                                  Если инициализировать в конструкторе есть гарантия, что в уже созданном объекте не будет null, иначе состояние, когда есть объект и там null — допустимо (в процессе десериализации)

                                                                                                    +1
                                                                                                    Вы не понимаете. Хватает ситуаций «я уверен, что на момент чтения оно не будет null, поэтому не заставляй меня делать его nullable, раз уж у компилятора не хватает ума самому это вычислить». В котлин для этого есть модификатор lateinit (по названию видно что он делает). Это и различные dto без конструкторов, методы вида Init, на границе с UI тоже пригождается и на границе с жавой (там ведь тоже никакого null safety). Вот прям такая же ситуация и с шарпом — множество обычного кода, который знать не знает про nullable

                                                                                                    Инициализация в конструкторе это костыль
                                                                                                    1. Я уже говорил, что эта инициализация просто нагружает GC лишний раз. Можно сказать что это короткоживущие объекты, но есть пункт 2
                                                                                                    2. Может быть совсем не просто инициализировать свойство, которое суть сложный объект, да еще с приватным/интернал конструктором (сериализатору это без разницы, а вот вы будете в тупике)
                                                                                                    3. И даже если можно, то в случае проблем с десериализацией вы можете получить неконсистентное состояние (новые данные + бесполезное дефолтные)

                                                                                                    А теперь про гарантии. Вдруг десериализатор не заполнит некоторые свойства. Сравните:
                                                                                                    а) В случае lateinit я просто сделаю проверку на null всех свойств 1 раз (и все) после десериализации. Я выполню гарантии в рантайме и у меня есть способ сказать компилятору «я сам».
                                                                                                    б) в случае «дефолтные в конструкторе» я не смогу отличить что новое, а что старое. Мне придется делать полный валидатор, что уже перебор. Также он может быть невозможен, если значение свойства не отличить — и тогда придется все делать nullable. А это приведет к тому, что в 1000 мест мне придется проставлять!

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

                                                                                                      Возможно, он просто не успел. В Котлине это сразу было или потом добавили?


                                                                                                      Логически правильно тогда либо не позволять создавать объекты такого типа никак кроме как десериализацией (и допустим вручную, но с заполнением всех полей). Либо десериализация возращает тип, который знает, что все поля заполнены. Хотя наверное, так все вообще сложно будет.

                                                                                                        0
                                                                                                        Возможно и не успел. Поэтому и спросил. На гитхабе сложно следить где что там решили, да и часто это просто болтовня, а не «решили».

                                                                                                        Пример реальной попытки: сериализатор требует пустого конструктора и заполняет только те свойства, что может заполнить (причем через internal сеттеры). Если не заполнены, то это нештатная ситуация и простой валидатор проверяет на null зная, что ни одно поле не может быть null. Сложный валидатор невозможен — ему тут не место. И все работает, все хорошо, только фатальный недостаток — не использует новых фич

                                                                                                        Пробую #nullable. Инициализация дефолтными значениями не прошла. Сразу поломался валидатор на незаполненность, а больше ничего и не проверить. Попытка обвесить модельки #nullable disable тоже ничего не дало. В итоге сделал свойства моделек nullable и начал массово (реально в сотнях мест) проставлять! но на полпути задался вопросом «а зачем?»

                                                                                                        Код остался прежним, но я получил требование в клинтском коде писать! (и необходимость помнить, что! обязателен, ведь non-null гарантирован логикой, nullable из-за компилятора), а вот выгоду я увидеть не смог

                                                                                                        и придумать как переделать, да чтобы не городить жуткие костыли из-за слабости поддержки nullable в языке, я тоже не смог, поэтому вернул все обратно.
                                                                                                          +1

                                                                                                          По-идее, клиентский код (в смысле business logic) не должен видеть все эти DTO, т.е. по хорошему у вас в доменной модели должен быть тип (скажем, class Person{}), который обеспечивает все гарантии nullability и прочего, и который конструируется из DTO (скажем, class PersonDTO{}) имеющий пустой конструктор, nullable поля и ноль гарантий, т.е. простой мешок пропертей. Валидация входных десериализованных данных происходит один раз в конструкторе Person(PersonDTO dto), а дальше гарантии распространяются через этот Person.

                                                                                                            +1
                                                                                                            Все зависит от точки зрения. Ваша цепочка выглядит примерно так:

                                                                                                            --> json --> десериализация в PersonDTO --> Person(PersonDTO dto) -->

                                                                                                            «десериализация в PersonDTO» может быть библиотекой-клиентом к некому апи, которую клиентский код может использовать. Она, можно сказать, транспортный слой, который при этом ничего не знает про business logic. И нормально, что этот слой валидирует полное получение данных, хоть и не может валидировать их суть. Я получаю ошибку сразу «fail fast», и к тому же на уровне, где она произошла.

                                                                                                            Если я получаю из api погоду в виде иммутабельного объекта Weather c 3мя свойствами «город, температура, влажность», то рассматривать Weather как dto и создать WeatherModel, или просто использовать как есть? — каждый решает сам, но в случае «использовать как есть» у нас все равно есть гарантия транспортного слоя, что «город не null» и «температура настоящая, а не дефолтная»

                                                                                                            И если переделка под nullable привносит только необходимость прописывать восклицательные знаки (или же отказ от проверок транспортного слоя), то я лучше откажусь от nullable
                                                                                                              0
                                                                                                              И нормально, что этот слой валидирует полное получение данных, хоть и не может валидировать их суть.

                                                                                                              Ну вот я собственно и сомневаюсь в том что транспортный слой должен давать какие-то гарантии модели, помимо типа и формата данных (число, строка, дата, объект, массив и т.п.). Это как бы не его роль. Его роль — тупо сконвертировать данные из одного представления в другое "as is" и дальше дать решать бизнес-слою, полные это данные или нет. Может оказаться, что ему и не нужна абсолютная полнота, что он терпит частичные данные, и если у Weather не будет температуры, то и фиг с ней, он её не использует.


                                                                                                              Тем более что частичные DTO — это распространённый метод обновления лишь некоторых выбранных значений вместо посылки полной модели..

                                                                                                                0
                                                                                                                Ну это мы в сторону ушли (моя вина). Не должен транспортный давать гарантии модели. Он должен давать гарантии получения данных «не потерялось и дошло как надо». Но мы ушли в обсуждение архитектуры, а ведь проблема куда проще.

                                                                                                                Речь идет о сериализаторе. К нему есть требование дать гарантию, что он заполнит все поля в классе, тип которого ему передадут для материализации (и они не будут дефолтными). Это у него постусловия такие. И сериализатор достаточно глуп, чтобы отследить это прямо в процессе разбора данных. Приходится проверять постусловием
                                                                                                                Эти постусловия четко отражены в спеке и следуя «концепции баррикады» можно не писать дополнительные проверки на это в клиентском коде и не получить NRE.

                                                                                                                и с #nullable в их текущей реализации выбор очевиден
                                                                                                                1 инициализировать дефолтные и поломать постусловия, от чего пострадает клиентский код (breaking change) — и все это с нулевой выгодой
                                                                                                                2 заставить в клиентском коде проставить везде "!" и заставлять в новом коде это писать — и снова выгоды не вижу
                                                                                                                3 оставить все как есть.

                                                                                                                  +1

                                                                                                                  Сериализатор говорит: у меня Weather.City всегда не null. И тут приходит HTTP PATCH с частично заполненным JSON, в котором нет поля city (потому что на клиенте оно не менялось). Какова реакция сериализатора? Выкинуть исключение, потому что, по мнению сериализатора, без city этих данных недостаточно для работы? А с чего он взялся решать такие вопросы? А бизнес-слой считает, что ему данных хватает, но между клиентом и ним сидит чересчур умный сериализатор и не даёт данным дойти.


                                                                                                                  Это у него постусловия такие.

                                                                                                                  Постусловия можно поменять. Может вы имели в виду тех-задание, против которого нельзя возразить и ничего нельзя поменять? Ну да, с таким тех-заданием придётся выкручиваться.

                                                                                                                    +1
                                                                                                                    Если бизнес логике хватает данных когда нет Weather.City значит соответствующее поле буде обозначено как нулейбл.
                                                                                                                    И проблемы нет
                                                                                                                      0

                                                                                                                      Сериализатор ничего не знает о бизнес-логике, поэтому он не в курсе, обязателен ли Weather.City или нет.

                                                                                                                      +2
                                                                                                                      А с чего он взялся решать такие вопросы
                                                                                                                      Именно так. Конечно можно сказать, что не дело сериализатора проверять заполненность и пусть проверяет уже на месте. Но если апи четко объявляет, что такого не будет (спека), то нарушение постусловия означает, что система работает неправильно.
                                                                                                                      Допустимо ли это и пропустить дальше или упасть «fail fast» зависит от приложения. Все эти «fail fast» обычно все исчезают в процессе разработки и тестирования. И если далее такое будет, то это ну совсем нештатная ситуация, которая требует немедленного внимания, потому что логике быть не должна ни в коем случае.

                                                                                                                      И да, поле с возможностью null будет nullable (или помечено аттрибутом, раз уж #nullable не зашел)

                                                                                                                      Постусловия можно поменять
                                                                                                                      Нельзя. Если имеется код, который завязан на контракт (спеку) кода сериализации, то ничего менять уже нельзя.

                                                                                                                      зы: конечно вы правы про то, что «каждый должен заниматься своим делом». Но подход fail-fast себя оправдывает, если ошибки, которые он ловит, можно починить до релиза. Если же это сервис, который должен быть устойчивый ко всем «а что если», то там предусматриваются все варианты, а не «зови программиста»
                                                                                                                        0

                                                                                                                        Так как у вас на выходе "сериализатора" не DTO, а фактически частично валидированные модели с частичными гарантиями (и у вас выходит уже не совсем сериализатор, а целый API), то если вам хочется донести эти гарантии до пользователя, вам придётся делать промежуточный слой из настоящих DTO, например:


                                                                                                                        JSON -->  JObject --> new Weather(JObject dto)

                                                                                                                        И тогда Weather может спокойно сделать свои поля non-nullable, а все проверки держать в своём конструкторе (а если валидация сложная и в один конструктор не влазит, то в фабрике).

                                                                                                                          0

                                                                                                                          Я, наверное, упустил контекст, но зачем вся эта ерунда, если прямо сериализатору можно сказать об ограничениях на значения через типы? Если там поля может не быть, то, значит, тип поля — Maybe a/std::optional<T>/etc.

                                                                                                                            0

                                                                                                                            В C# не завезли Optional/Maybe из коробки :(, поэтому популярные библиотеки сериализации работают либо со строго-типизированным POD через рефлексию, либо создают dynamic (объект "динамического типа", который создаётся в рантайме — примерно как Object в JavaScript), либо выдают значения через какую-нибудь кастомную обёртку вроде упомянутого NewtonSoft JObject. Последние два способа — это фактически Dictionary<string,object> под разными соусами, и наличие/отсутствие значения проверяется через наличие/отсутствие ключа. Первый способ — более строгий, но без Optional в стандарте приходится извращаться либо со "специальным значением" (0, "", null, default(T)) в случае отсутствия поля, либо вводить дополнительное булево свойство вроде:


                                                                                                                            [XmlElement(IsNullable = false)
                                                                                                                            public string City;
                                                                                                                            [XmlIgnore]
                                                                                                                            public bool CitySpecified;

                                                                                                                            Такая вот попоболь.

                                                                                                                              0

                                                                                                                              Не очень понял, за что минус. Конечно, вы можете написать свой Optional и свой сериализатор (как я сделал для своих задач), но это уже велосипедостроительство. Будет сильно лучше, если эти типы будут присутствовать в стандартной библиотеке в пространстве имён System.

                                                                                                                                0

                                                                                                                                Я вообще не могу рассуждать о C# и .NET, но для своего ORM'а на плюсах я сделал обёртки типа PKey, Unique, NotNull и подобные, которые движку сообщают всю нужную семантику.


                                                                                                                                Но за что минус, я тоже не понимаю.

                                                                                                                                  0

                                                                                                                                  Чем именно вот это https://github.com/microsoft/referencesource/blob/master/mscorlib/system/nullable.cs отличается от option?

                                                                                                                                    0

                                                                                                                                    Тем, что where T : struct.

                                                                                                                                      0

                                                                                                                                      Ну, а для ссылочных типов null есть. Фактически для них только Option был до 8.0

                                                                                                                                        0

                                                                                                                                        Во многих задачах значение null не эквивалентно отсутствию значения.

                                                                                                                                          0
                                                                                                                                          Да, но это очень сложно доносить до людей, т.к. при использовании Option появляется дополнительный уровень indirection, а синтакстса для распаковки оных, в отличии от например Swift-а, в C# еще нет.
                                                                                              +5
                                                                                              Появление var, lambda функций, linq, nullable VALUE types, tuples, nameof, async await, $"..." — как же это все было круто! Благодаря этим и многим другим фичам C# сегодня — это, на мой взгляд, лучший язык программирования.
                                                                                              Но Nullable Reference Types — это какой-то перебор и шаг не туда.
                                                                                              Это очень странное решение, которое выливается в множество неочевидных нюансов, включая обратную совместимость с существующим кодом, отсутствие поддержки в runtime (хоть это и обещают сделать в следующих версиях, интересно как?) и многое другое. Уже сейчас, когда новой версией языка по сути еще никто не пользовался, это нововведение уже вызвало некий раскол среди разработчиков, т.к. плюсов и минусов у этого решения хватает.
                                                                                              Интересно, что Nullable Value Types (int?) такой реакции не вызывали, они очень органично «зашли» в язык.
                                                                                                +2
                                                                                                Действительно, реализация получается пока-что весьма неоднозначной (не стоит забывать, правда, что оно ещё в бете, и может поменяться). Особенно смущает то, что сам по себе non-nullable reference тип никак не гарантирует, что в этой переменной не придёт в рантайме null, только из-за того, что кто-то где-то поставил один лишний '!'. Вот интересный взгляд на nullable reference с точки зрения статического анализа кода, и как он влияет на его работу.
                                                                                                +4
                                                                                                Nullable Reference Types — то, что должно быть изначально. В котлине он изначально и там с этим очень неплохо. А вот в c#… прямо сейчас я чиню очередной NRE, причем в библиотеке, а ведь его могло бы и не быть, будь от этого защита.

                                                                                                и «поддержка в runtime» не нужна. Ведь основная задача этой функциональности — позволить на этапе написания кода избегать NRE с нулевой стоимостью во время выполнения.

                                                                                                Просто «поздно решили внедрить» и поэтому приходится делать это костыльно
                                                                                                  +3

                                                                                                  Undefined behavior в C — это совсем не то. Это когда are[-1] в коде может привести к чему угодно — от вычитывания любого элемента из массива, до восстания SkyNet.


                                                                                                  У вас же — возможно surprising behavior, но ни как не undefined.

                                                                                                    0
                                                                                                    Строго говоря вы правы, не спорю. Но результат один: придется дебажить.
                                                                                                    +4

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

                                                                                                      –6
                                                                                                      Это вполне нормальное явление на Хабре: люди, не имеющие возможности приобрести даже самый дешевый автомобиль от «Tesla», долго и со смаком рассуждают о преимуществах Model X, или«учат» Илона Маска, как нужно вести бизнес, или делать ракеты. А уж сколько появляется «экспертов» в комментариях любой «болтологической» статьи (будь-то про job interview, жизни за рубежом, seniors vs juniors etc.)…
                                                                                                        +7
                                                                                                        Ну, люди пишут в соответствии с тем фактом, что им с этими фичами работать.
                                                                                                          –6
                                                                                                          Ой, я вас умоляю, «работать»… Если человек реально заинтересован в какой-то новой фиче, уверен в своей правоте, и может эту правоту доказать не «минусованием кармы» оппонента (на гитхабе анонимные пакостники не в почете), а аргументированным feature request-ом, а, еще лучше, pull request-ом, то он не будет «чесать» свое ЧСВ тут, в комментариях — что, по сути, занятие бессмысленное и полностью бесполезное. Но я глубоко сомневаюсь, что кто-то из здешних комментаторов выдал хоть один PR в "репу".
                                                                                                        0
                                                                                                        Дефолтные реализации интерфейсов — это, на самом деле, почти миксины. Именно для этого создавался пропоузал, и это абсолютно замечательная фича.

                                                                                                        А вот, например, паттерматчингом я никогда не пользовался (разве что is null вместо == null) и вообще не вижу зачем он, но мб зачем-то и нужен.
                                                                                                          +2
                                                                                                          Поведенческие миксины уже существуют давно как методы-расширения-на-интерфейсах.
                                                                                                          +1
                                                                                                          хотя конечно в C# запись x[-1] лишена всякого смысла

                                                                                                          Если речь только только про массивы, то да. Если про C# вообще, то нет. Смысл зависит от того, что есть x. Самописный индексатор можно делать всё что хочет. Даже без самописных индексаторов я собственными руками писал x[-1], где x был указателем.

                                                                                                            0
                                                                                                            Встроенные типы, такие как массивы или строки, конечно же поддерживают индексер (operator this[]) для типа Index, так что смело можно писать
                                                                                                            var items = new[] { 1, 2, 3, 4, 5 };
                                                                                                            items[^2] = 33; // 1, 2, 33, 4, 5

                                                                                                            Проверяю:


                                                                                                            var items = new[] { 1, 2, 3, 4, 5 };
                                                                                                            Print(items);
                                                                                                            items[^2] = 33;
                                                                                                            Print(items);

                                                                                                            0)  1,2,3,4,5
                                                                                                            1)  1,2,3,33,5
                                                                                                              0
                                                                                                              Смею предположить, что вы проверяете на VS2019 RTM. А надо на Preview.
                                                                                                                0
                                                                                                                Microsoft Visual Studio Community 2019 Preview
                                                                                                                Version 16.2.0 Preview 1.0

                                                                                                                NET Core 3.0.100 Preview 5
                                                                                                                  –1
                                                                                                                  Есть статья на английском хабре, там сказано:
                                                                                                                  There is no compatibility guarantee from one C# 8.0 preview to another.
                                                                                                                  In short, if you use C# 8.0 preview in Visual Studio 2019, some features and behavior may change between now and when C# 8.0 fully releases.

                                                                                                                  Источник: habr.com/ru/company/microsoft/blog/443000
                                                                                                                    –1
                                                                                                                    Значит то Preview которое у вас — не самое свежее.
                                                                                                                      0

                                                                                                                      Эти VS и NET Core Preview до сих пор самых свежие из доступных. Разве что ещё nightly builds какие-нибудь есть.

                                                                                                                  +2

                                                                                                                  Это, кстати, соответствует майкрософтовской документации (см мой коммент выше), что ^0 это индекс за-концом массива (а не последний как в статье), ^1 это последний элемент, а ^2 предпоследний.

                                                                                                                    +1
                                                                                                                    Эти результаты правильны согласно официальной документации:
                                                                                                                    Для любого числа n индекс ^n совпадает с sequence.Length — n


                                                                                                                    Возможно, в предыдущих Preview было иначе.
                                                                                                                    0
                                                                                                                    Представьте массив x = {1, 2, 3}. Если взять x[0..2] вы получите {1, 2}, а вот если взять x[..2] или например x[..], вы получите {1, 2, 3}.

                                                                                                                    Проверяю:


                                                                                                                    // Представьте массив x = {1, 2, 3}.
                                                                                                                    var x = new[] { 1, 2, 3 };
                                                                                                                    
                                                                                                                    // Если взять x[0..2] вы получите {1, 2}
                                                                                                                    Print(x[0..2]);
                                                                                                                    
                                                                                                                    // а вот если взять x[..2] или например x[..], вы получите {1, 2, 3}.
                                                                                                                    Print(x[..2]);
                                                                                                                    Print(x[..]);

                                                                                                                    0)  1,2
                                                                                                                    1)  1,2
                                                                                                                    2)  1,2,3
                                                                                                                      0
                                                                                                                      Тут я действительно ошибся. Конечно ..2 даст тот же результат, должно было быть ..^0.
                                                                                                                        0

                                                                                                                        0..^0 это тоже весь массив.
                                                                                                                        Тогда уже 0..^1.

                                                                                                                      +3
                                                                                                                      дизайнеры всех современных языков «продолбали» как минимум несколько важных аспектов [...] zero-terminated strings, когда длина строки вычисляется за О(n).
                                                                                                                      Ну, не так уж и всех, а лишь тех, кто решил не заморачиваться с нормальным строковым типом и обойтись char[]. В любом приличном современном языке (да и не только современном, вспомните тот же Паскаль) строки — один из базовых типов, который неплохо оптимизирован (во всяком случае, для более-менее коротких строк никто длину не вычисляет).
                                                                                                                        –3
                                                                                                                        Норму по нововведениям выполнили. Мне кажется скоро весело придут к обертке над ссылочными типами которая не может быть null. CanNotBeNullable<>
                                                                                                                          –3
                                                                                                                          Как я упомянул в посте, любой ссылочный тип — nullable by definition, так что ваше предложение не логично по сути.
                                                                                                                          +4

                                                                                                                          В принципе, попробовал все перечисленные фичи.


                                                                                                                          Мнение:


                                                                                                                          • nullable — сделано совсем через задний проход. Если по дефолту сделать nullable, в происходящем появится хоть какой-то смысл. Имхо сильно на любителя.
                                                                                                                          • Index & Range — звучит безумно, но пальцы привыкают быстро.
                                                                                                                          • Default interface members — минимум 90% разработчиков это не понадобится никогда. И слава богу.
                                                                                                                          • Pattern matching — ура. просто ура.
                                                                                                                            +2
                                                                                                                            Если по дефолту сделать nullable
                                                                                                                            Ну так оно и так во всем существующем коде по дефолту nullable. И все эти годы так было. И именно это «по дефолту null и инициализации не было и никто не подсказал про забытую инцицализацию» и является основной причиной ошибок NRE.

                                                                                                                            Проблема NRE в том, что вроде есть стектрейс, но он бесполезен, потому что я не знаю через сколько уровней и откуда пришла переменная, а воспроизвести не получается. А так если бы каждое место не допускало null, то мне бы еще компилятор указал где это и это было бы далеко от места реального NRE

                                                                                                                            А так лучше бы они records сделали — крайне полезная вещь.
                                                                                                                              0
                                                                                                                              Лучше бы они records сделали
                                                                                                                              Это да. Я очень расстроился, когда понял, что все слухи о них оказались слухами.

                                                                                                                              А по поводу nullable моя претензия заключается в том, что данные, приходящие из legacy (#nullable disable) кода, почему-то считаются non-nulable, хотя обычно это не так. И за счет этого мечта о "прощай, внезапный NRE" остается мечтой.

                                                                                                                              0

                                                                                                                              У меня реакция такая:


                                                                                                                              • Nullable ― Ура! Наконец. Теперь ещё период всеобщей адаптации пережить и будет счастье.
                                                                                                                              • Index & Range ― Кому-то реально не хватало? Ну ладно, пусть.
                                                                                                                              • Default interface members ― whatever
                                                                                                                              • Pattern matching ― За if (x is string s) большое спасибо, а для чего всё остальное?
                                                                                                                              • Switch expressions ― Спасибо.
                                                                                                                              • Async streams ― Может быть, когда-нибудь.
                                                                                                                              • Using declaration ― Кое-где бы пригодились, а то иначе некрасиво.
                                                                                                                              • Static local functions ― Предположим, кому-то это принципиально.
                                                                                                                              • Disposable ref structs ― Кому-то точно это принципиально.
                                                                                                                                0
                                                                                                                                if (x is string s) было еще в предыдущих версиях языка.
                                                                                                                              +1
                                                                                                                              Как же жаль, что Котлин так и не запилили под дотнет (
                                                                                                                                +1

                                                                                                                                Родному F# трудно играть на одном поле с C#. Всё взлетает, взлетает, никак не взлетит. Последний недостаточно плох, чтобы с него дёргаться. Котлин, на сколько я его себе представляю, это что-то а-ля C# или нечто среднее между C# и F#, лишь бы забыть про Яву.


                                                                                                                                Котлин под дотнет мог бы быть способом перетащить людей с платформы Java на Net. Кому-то это надо?

                                                                                                                                  +1

                                                                                                                                  Я думаю, также C# недостаточно плох по сравнению с Котлин тоже. Так что котлину придетбя бороться с C# для которого есть большая поддержка

                                                                                                                                    +4

                                                                                                                                    Проблема развития старых языков, в т. ч. c# — в необходимости сохранять обратную совместимость. Поэтому новые фичи (e. g. Nullable references) выглядят как костыли, а язык превращается в монстра(полюбуйтесь на с++).

                                                                                                                                    0
                                                                                                                                    И на другие языки, например Scala, тоже были надежды что сделают под CLR. Но если это делать, придется конкурировать с C#-ом, а это практически невозможно, т.к. по сути это конкуренция с МС со всеми вытекающими.
                                                                                                                                      0

                                                                                                                                      Какими вытекающими? JetBrains сделали Rider — конкурент VS, причём успешный.
                                                                                                                                      По-вашему под clr, кроме шарпа, не может быть юзабельных языков?

                                                                                                                                        0

                                                                                                                                        Юзабельные представить можно, тем более что в NET они есть. Юзабельные и не нишевые ― не очень представляю. И не очень представляю мотивацию, зачем одним упираться делать такой же, но другой язык (плюс инструментарий, без которого он сразу никому не нужен), а другим его использовать вместо существующего мейнстримового C#. Если бы последний был уродцем, которого все просто терпят до появления альтернативы, то да. А так нет.


                                                                                                                                        Rider ― это ещё одна хорошая IDE для NET. [Глупая_аналогия]Открыть ещё одну конкурирующую булочную, думаю, сильно проще, чем конкурировать с самим хлебом.[/Глупая_аналогия]

                                                                                                                                          0

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

                                                                                                                                            +1

                                                                                                                                            Я про Яву слышал другое.

                                                                                                                                              +1
                                                                                                                                              Принципиально разная ситуация. Одна из важных (может даже самая важная) платформа для Java — это Android. Разработчики которого очень сильно не ладят с разработчиками Java. Потому использовать тот факт, что Java — «родная», а Kotlin — «неродной»… не получится.

                                                                                                                                              В случае с же CLR и C# все популярные платформы контролируются Microsoft, которому продвигать что-то, кроме C# — совершенно неинтересно.
                                                                                                                                                0

                                                                                                                                                Почему неинтересно? А если это сделает разработку под их экосистему комфортнее?

                                                                                                                                                  +1
                                                                                                                                                  А непонятно куда комфортнее. В C# итак хорошо. Плюс у МС есть ряд нишевых .NET языков в Research, это F#, C-omega, P/Zing, Q# в конце концов. Какие-то их тех идей мигрируют в C#, самый яркий пример это фичи F#-а которые постепенно появляются в C# (так глядишь и до функциональных списков дойдем).
                                                                                                                                            +1
                                                                                                                                            Может, конечно может. Просто это титаническая работа которая должна давать компании, которая это реализует, некоторую отдачу. Можно ли делать это силами комьюнити — не уверен, под .NET тоже были разные попытки делать языки (например Boo и Nemerle), они достигли некой зрелости но сообщество не стало ими массово пользоваться, хотя даже JetBrains косвенно инвестировали в свое время в Nemerle (проект Nitra).
                                                                                                                                        +3
                                                                                                                                        Про индексы, и прочее. Там все немного поменялось же:
                                                                                                                                        In the previous preview, the framework supported Index and Range by providing overloads of common operations, such as indexers and methods like Substring, that accepted Index and Range values. Based on feedback of early adopters, we decided to simplify this by letting the compiler call the existing indexers instead. The Index and Range Changes document has more details on how this works but the basic idea is that the compiler is able to call an int based indexer by extracting the offset from the given Index value. This means that indexing using Index will now work on all types that provide an indexer and have a Count or Length property. For Range, the compiler usually cannot use an existing indexer because those only return singular values. However, the compiler will now allow indexing using Range when the type either provides an indexer that accepts Range or if there is a method called Slice. This enables you to make indexing using Range also work on interfaces and types you don’t control by providing an extension method.
                                                                                                                                          0
                                                                                                                                          Поведение индексов поменялось, так что на текущий момент в RTM и Preview они работают по-разному. Ну и плюс в RTM нехватает некоторых типов, которые нужно добавить вручную. Именно поэтому я рекомендую установку Preview.
                                                                                                                                          0
                                                                                                                                          Классная статья, очень интересно читать, хотя я даже не пишу на сишарпе
                                                                                                                                            +1
                                                                                                                                            Ну, возможно эта статья замотивирует вас писать на шарпе. Хотя фичи выше — не те которые должны мотивировать.
                                                                                                                                            +1
                                                                                                                                            Знатоки, напишите статью, и поделитесь пожалуйста примерами до чего дошел Pattern Matching в F# или других языках развивающих эту концепцию? Хотелось бы понять «о чем мечтает команда разработчиков C#» и к чему стремится прогресс. И конкретно, а где нибудь уже дошли до разбора выражений языка средствами языка?

                                                                                                                                            Отдал был часть кармы если бы Хабр позволял «краудфайндить» на статьи.
                                                                                                                                                +1
                                                                                                                                                Документация конкретной реализации не дает виденья. У меня остался возможно наивный вопрос, вот эта фича развивается от релиза к релизу, так что ее двигает и куда ее двигают? Как из заявленной решаемой задачи «разложения данных на составные части или извлечения информации из данных различными способами» — например пусть это будет конкретно Natural Language Processing — вырастает запрос на pattern-matching? Никак не вырастает потому что программеры работающие с этой задачей (NLP) работают с функциями своих NLP шных библиотек и мыслят в них. Зачем им новые statementы?

                                                                                                                                                Если бы проще заявили «немного полезного синтаксиса над рефлекшном» я бы не мучился, а так остается недоумение — что я упускаю.

                                                                                                                                                  0

                                                                                                                                                  При чём здесь рефлекшен?

                                                                                                                                                    0
                                                                                                                                                    Еще предстоит разобраться что было с головой, что так повело рассуждение. Ошибка. Спасибо что обратили внимание. Сейчас бы написал «немного полезного синтаксиса вокруг условных присваиваний».
                                                                                                                                                +1