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




C# 11 уже близится к завершению. Этот пост посвящён функциям версии Visual Studio 17.3 или тем функциям, которые не освещались в апрельском и февральском выпусках Visual Studio.


Новинки предварительной версии сводятся к трём основным аспектам, на которые мы обращаем особое внимание при разработке C# 11:


  • Улучшенная инициализация объектов: поддержка конструкторов и инициализаторов объектов стала проще при любых правилах для модифицируемых и немодифицируемых членах. Вот элементы новой функциональности языка:
  • Обязательные члены (ключевое слово required).
  • Поля ref.
  • Поддержка универсальной математики: вы можете использовать один написанный вами алгоритм со многими числовыми типами. Это упрощает сбор статистики, машинное обучение и другие прикладные задачи C# и .NET, требующие внушительного объёма вычислений. В том числе предлагаются:
  • Статические члены интерфейса: абстрактные и виртуальные.
  • Ослабление требований к сдвигу вправо.
  • Оператор беззнакового сдвига вправо.
  • Числовой оператор IntPtr.
  • Продуктивность разработчика: новые возможности языка сделают вашу работу продуктивнее. В первую очередь расширена область применения nameof.

Все эти функции описаны ниже со ссылками на разделы документации, где вы можете узнать о них больше. Чтобы протестировать эти возможности, включите в своём проекте функцию предварительного просмотра. Объяснения приводятся в статье «Новые возможности C# 11».


Улучшенная инициализация объектов


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


public class Person
{
    public string FirstName { get; init; }
    public string LastName {get; init; }
}

Вызывающая сторона должна иметь инициализаторы объектов, которые задают свойства FirstName и LastName. До версии 17.3 компилятор не мог заставить вызывающую сторону устанавливать эти свойства, тогда как конструктор с обязательными параметрами — единственный способ убедиться в том, что пользователь задал свойства FirstName и LastName. Ключевое слово required сообщает компилятору и вызывающей стороне, что свойства должны быть заданы:


public class Person
{
    public required string FirstName { get; init; }
    public required string LastName {get; init; }
}

Вызывающая сторона должна включать инициализаторы объектов свойств FirstName и LastName, иначе компилятор сообщает, что обязательные члены не заданы, и выдаёт ошибку. Таким образом, разработчик решит эту проблему сразу, а не будет возвращаться к ней на этапе сдачи проекта.


Если тип Person использовался с компилятором прошлых версий и включает конструктор для задания свойств, это не помешает вам использовать обязательные члены. Для их применения существующие конструкторы должны использовать атрибут SetsRequiredMembers:


public class Person
{
    public required string FirstName { get; init; }
    public required string LastName {get; init; }

    [SetsRequiredMembers]
    public Person(string firstName, string lastName)
    {
        this.FirstName = firstName;
        this.LastName = lastName;
    }

    public Person() {}
}

Атрибут SetsRequiredMembers указывает, что конструктор задаёт все обязательные параметры. Компилятор узнаёт, что вызывающая сторона, которая использует Person(string firstName, string lastName), установили все требуемые члены. Конструктор без параметров не включает и этот атрибут, поэтому для вызывающей стороны, которая использует этот конструктор, нужно инициализировать все обязательные члены.


В примерах выше изменения касались свойств. Но обязательные члены применимы и к объявлению полей.


В предварительной версии в начальном виде реализованы также значения полей ref и scoped. Теперь поля ref можно использовать в типах ref struct. Ключевое слово scoped позволяет ограничить время жизни параметров ref. На данный момент описания функций и изменений в новой версии — лучшая доступная документация по этой теме. Мы обнаружили несколько ситуаций, где для безопасного применения новой фичи потребовались обновления языка. Эти обновления появятся в более поздней предварительной версии, а документация по ней будет отражать функциональность окончательной версии.


Поддержка математики с обобщёнными типами


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



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


Компилятор разрешает вызовы методов static, включая операторы, во время компиляции. При этом механизма динамической диспетчеризации, как для методов экземпляра, в этом случае нет. В доках конкретные правила записи этой функции объясняются подробнее.


Другие возможности языка сглаживают некоторые различия числовых типов и облегчают написание обобщённых математических алгоритмов. Оператор сдвига вправо больше не требует, чтобы второй операнд имел тип int. Подойдёт любой целочисленный тип данных! Типы nint и nuint — это синонимы System.IntPtr и System.UIntPtr соответственно. Вместо соответствующих типов можно использовать эти ключевые слова, а новые анализаторы позволяют и даже побуждают делать это. Наконец, оператор беззнакового сдвига вправо (>>>) позволяет избежать приведения типа при выполнении беззнакового сдвига.


Эти и другие изменения повлияли на работу математики с обобщёнными типами в рантайме. К другим изменениям относятся, например, пользовательские операторы с проверкой переполнения. Улучшения языка позволяют команде рантайма эффективнее использовать все числовые типы .NET. Эти возможности пригодятся и в реализации контрактов с помощью операторов или других статических методов.


Продуктивность разработчиков


Теперь nameof можно применять вместе с параметрами методов. То есть вы можете использовать nameof при объявлении атрибутов метода:


[return: NotNullIfNotNull(nameof(url))]
string? GetTopLevelDomainFromFullUrl(string? url)

Попробуйте сами


Предлагаем вам скачать предварительные версии Visual Studio 2022. Вы также можете скачать последнюю предварительную версию .NET 7. После установки вы сможете сами оценить новые функции. Для этого создайте или откройте проект в C# и задайте тегу LangVersion значение Preview.


Предварительная версия Visual Studio приближает нас к полному набору функций C# 11. При разработке новой версии языка мы продолжаем вкладываться в развитие различных аспектов. По вашим отзывам мы внесли корректировки, а теперь — самое время скачать предварительную версию Visual Studio, протестировать новые функции и рассказать нам, что вы думаете о ней. Мы внимательно слушаем вас, пока работаем над окончательными версиями обновлений C# 11 и .NET 7.


Стать востребованным профессионалом в IT с самого начала или прокачаться помогут наши курсы. Скидка 45% по промокоду HABR:



Скидка 45% по промокоду HABR

Комментарии (18)


  1. tzlom
    08.09.2022 01:02
    +3

    А какой смысл в SetsRequiredMembers если компилятор сам может эту проверку выполнить?


    1. Skykharkov
      08.09.2022 02:53

      Кстати да, тоже непонятно. Но думается мне, что это для того чтобы убедится что поле точно инициализируется в хоть каком-то конструкторе. Сначала required объявили и если нет инициализации - "хоть тушкой, хоть чучелком" выкатываем ошибку. А [SetsRequiredMembers] явно указывает, что тут мы "сейчас будем играть во все игры". Раньше можно было warning'ами это отследить ("is null here") а теперь можно как ошибку засетапить. Наверное будет хорошо в REST запросах и так далее. А то там вечно бардак с параметрами...


    1. 0xd34df00d
      08.09.2022 04:56
      +2

      Не всегда — проблема останова.


      1. tzlom
        08.09.2022 07:51
        +5

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


        1. stranger777
          08.09.2022 12:52

          Хм… "Если компилятор не смог вывести факт инициализации то его можно зафорсить этим аттрибутом". Разве это не означает, что компилятор не смог вывести факт инициализации без атрибута, но атрибут явно указывает (заставляет компилятор увидеть), что члены есть и всё-таки "выводит факт их инициализации". То есть задаёт члены, задать которые было необходимо, а раньше он их вообще не видел. Во всяком случае, уточнили, спасибо.


      1. mayorovp
        08.09.2022 15:46

        В языке C# уже есть понятие Definite Assignment, к которому все привыкли. Можно было бы его переиспользовать для свойств.


    1. md_backend_binance
      08.09.2022 12:22

      для типов налэбл, когда не ясно налейбл это установка или налэбл это присвоение


      1. tzlom
        08.09.2022 13:28

        на сколько я понял для Nullable там своя история и проверки эти не зависимые


    1. ShadowTheAge
      08.09.2022 14:45

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


      1. tzlom
        08.09.2022 15:26

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


        1. qw1
          09.09.2022 10:00

          Получается, что нужна.
          Если класс Person (из статьи) объявлен в отдельной сборке, как компилятор поймёт без атрибута на конструкторе, что

          new Person();

          запрещена, а
          new Person("John", "Smith");

          разрешена?
          Декомпилировать код конструктора и смотреть, что он заполняет, а что нет?


          1. tzlom
            09.09.2022 10:35
            -1

            У вас эта сборка не соберется если вы там наколбасите.


            1. qw1
              09.09.2022 10:47
              +2

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

              new Person { FirstName = "John", LastName = "Smith" };


      1. qw1
        08.09.2022 16:33
        +2

        Непонятно. Тип, объявленный в сторонней библиотеке, не подлежит модификации. На конструктор мы не навесим атрибут, а поля не сможем пометить ключевым словом required.


        1. ShadowTheAge
          08.09.2022 19:13
          +2

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


          1. qw1
            09.09.2022 10:02

            А, вот в чём дело…


  1. Mingun
    08.09.2022 17:18
    +2

    А кто это за звери — «операторы вызова»?


    1. mayorovp
      08.09.2022 18:46
      +1

      that require callers to set certain properties

      Угу, две загадки: как "звонящий" стал "оператором вызова" и куда вообще потерялись свойства.