[Перевод] Поговорим о фичах в предварительной версии C# 11

К старту курса по разработке на C# рассказываем о новых конструкциях в предварительной версии языка C# 11. Среди них шаблоны списка, проверка Parameter на null и возможность переноса строки при интерполяции строк. За подробностями приглашаем под кат.


Visual Studio 17.1 (Visual Studio 2022 Update 1) и .NET SDK 6.0.200 содержат предварительный функционал C# 11. Чтобы поработать с ним, обновите Visual Studio или загрузите последний SDK .NET. Также посмотрите пост о выходе Visual Studio 2022 17.1, чтобы узнать о нововведениях в Visual Studio, а также пост об анонсе .NET 7 Preview 7.

Проектирование C# 11

Мы любим проектировать и разрабатывать открыто. Предложения по функциональности C# и заметки о проектировании языка вы найдёте в репозитории CSharpLang. Главная страница объясняет процесс проектирования, а ещё можно послушать Мэдса Торгерсена на .NET Community Runtime and Languages Standup, где мы говорим о процессе проектирования.

Как только работа над функцией спланирована, она вместе с отслеживанием прогресса переносится в репозиторий Roslyn. Статус ближайших функций вы найдёте на странице Feature Status. Там вы увидите, над чем мы работаем и что добавляем во все предварительные версии, а ещё можно посмотреть, что вы могли пропустить.

Из этого поста я убрала сложные технические детали и технические дискуссии о значении каждой новой конструкции языка в вашем коде. Надеемся, что вы попробуете эти предварительные функции и дадите нам знать, что думаете о них. Чтобы попробовать новые конструкции C# 11, создайте проект и установите значение тега LangVersion в Preview.

Файл .csproj будет выглядеть так:

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <LangVersion>preview</LangVersion> </PropertyGroup>
</Project>

Символ новой строки в элементах формата интерполированных строк

Прочитать об этом изменении больше можно в предложении Remove restriction that interpolations within a non-verbatim interpolated string cannot contain new-lines. #4935

C# поддерживает два стиля интерполированных строк: буквальный и небуквальный, $@»» и $»» соответственно. Ключевое различие между ними заключается в том, что небуквальные интерполированные строки не могут содержать символ новой строки в сегментах текста, вместо этого должен использоваться символ экранирования (например, \r\n).

Буквальные интерполированные строки в своих сегментах могут содержать символы новых строк без экранирования. Символы "" экранируют кавычки. Всё это поведение остаётся неизменным.

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

Элементы формата сами по себе — не текст, они не должны следовать правилам экранирования и переноса строк для интерполированных сегментов строчного текста. Например, код ниже может привести к ошибкам в C# 10, но он корректен в предварительной версии C# 11:

var v = $"Count ist: { this.Is.Really.Something() .That.I.Should( be + able)[ to.Wrap()] }.";

Шаблоны списка

Больше об этом изменении читайте в предложении о списке шаблонов.

Новый шаблон списка позволяет сопоставлять списки и массивы. Можно сопоставлять элементы и опционально включать шаблон слайса, который сопоставляет 0 и более элементов. При помощи этого шаблона можно отбросить или захватить 0 или более элементов.

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

В коде ниже шаблон [1, 2, .., 10] сопоставляет всё:

int[] arr1 = { 1, 2, 10 };
int[] arr1 = { 1, 2, 5, 10 };
int[] arr1 = { 1, 2, 5, 6, 7, 8, 9, 10 };

Чтобы разобраться с шаблоном списка, посмотрите этот код:

public static int CheckSwitch(int[] values) => values switch { [1, 2, .., 10] => 1, [1, 2] => 2, [1, _] => 3, [1, ..] => 4, [..] => 50 };

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

WriteLine(CheckSwitch(new[] { 1, 2, 10 })); // prints 1
WriteLine(CheckSwitch(new[] { 1, 2, 7, 3, 3, 10 })); // prints 1
WriteLine(CheckSwitch(new[] { 1, 2 })); // prints 2
WriteLine(CheckSwitch(new[] { 1, 3 })); // prints 3
WriteLine(CheckSwitch(new[] { 1, 3, 5 })); // prints 4
WriteLine(CheckSwitch(new[] { 2, 5, 6, 7 })); // prints 50

Также можно захватить результаты шаблона слайса:

public static string CaptureSlice(int[] values) => values switch { [1, .. var middle, _] => $"Middle {String.Join(", ", middle)}", [.. var all] => $"All {String.Join(", ", all)}" };
  • Шаблоны списка работают с любым счётным и индексируемым типом. Это означает, что шаблонам доступны свойства Length или Count, а у индексатора — параметр int или System.Index. 

  • Шаблоны слайса работают с любым нарезаемым типом. Это означает, что шаблону доступен индексатор, аргументом принимающий Range или метод Slice с двумя параметрами int.

Мы рассматриваем добавление поддержки шаблона списков типов IEnumerable. Если у вас есть возможность поэкспериментировать с этой фичей, дайте нам знать, что вы думаете о ней.

Проверка Parameter на null

Узнать об этом изменении больше можно в предложении Parameter null checking. Мы поместили эту фичу в предварительную версию C# 11, чтобы у нас было время получить отзывы. По поводу крайне лаконичного и более подробного синтаксиса хочется получить отзывы клиентов и пользователей, которые успели поэкспериментировать с этой функцией.

Довольно часто для проверки аргумента на null используются такие вариации бойлерплейта:

public static void M(string s)
{ if (s is null) { throw new ArgumentNullException(nameof(s)); } // Body of the method
}

С помощью проверки Parameter на null можно сократить код, добавив к имени параметра !!:

public static void M(string s!!)
{ // Body of the method
}

Здесь генерируется код проверки на null. Выполняться он будет перед выполнением любого кода внутри метода. В конструкторах проверка на null происходит перед инициализацией поля, вызовом базовых конструкторов и вызовом конструкторов через this.

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

Сама проверка — эквивалент кода if (param is null) throw new ArgumentNullException(...). Когда !! содержат несколько параметров, операторы !! выполняются в порядке объявления этих параметров.

Вот несколько рекомендаций, ограничивающих применение !!:

  • Проверки применимы только к имеющим реализацию параметрам. Их не может быть, например, у параметра абстрактного метода.

 Другие случаи, где проверку использовать нельзя, включают:

  • параметры метода с extern;

  • параметры делегата;

  • параметры интерфейсного метода, когда нет интерфейсного метода по умолчанию;

Проверку можно применить только к параметрам, которые возможно проверить.

Пример ситуаций, которые исключаются на основании второго правила, — переменные _ и параметры out. Проверка на null возможна для параметров ref и in. Она разрешена для параметров индексатора и добавляет в акцессоры get и set:

public string this[string key!!] { get { ... } set { ... } }

Применима проверка и к параметрам лямбда-выражений, со скобками или без — не важно:

// An identity lambda which throws on a null input
Func<string, string> s = x!! => x;

Асинхронные методы также могут иметь проверяемые на null параметры. Параметр проверяется при вызове метода.

Новый синтаксис проверки на null допустим в методах итератора. Параметр проверяется при вызове метода итератора, но не при проходе нижележащего перечислителя. Это верно для традиционных асинхронных итераторов:

class Iterators { IEnumerable<char> GetCharacters(string s!!) { foreach (var c in s) { yield return c; } } void Use() { // The invocation of GetCharacters will throw IEnumerable<char> e = GetCharacters(null); }
}

Итераторы с обнуляемыми ссылочными типами

Любой имеющий в начале имени !! параметр вначале будет иметь отличное от null значение. Это верно, даже если тип параметра сам по себе потенциально null. Такое может случиться с явно обнуляемым типом, скажем, string, или с параметром типа без ограничений. Если синтаксис !! в параметре комбинируется с явно обнуляемым типом этого параметра, компилятор выдаст предупреждение:

void WarnCase<T>( string? name!!, // CS8995 Nullable type 'string?' is null-checked and will throw if null. T value1!! // Okay
)

Конструкторы

При переходе от явных проверок на null к проверке через оператор !! есть небольшое, но заметное изменение. Явная проверка происходит после инициализации полей, конструкторов базового класса и вызовов конструкторов через this.

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

Заметки о дизайне

Может быть, вы слышали Джареда Парсонса на .NET Languages and Runtime Community Standup 9 февраля 2022 года. Ролик начинается примерно на 45-й минуте стрима, когда Джаред присоединяется к нам, чтобы рассказать подробности о решениях по функциональности предварительной версии C# 11 и ответить на некоторые распространённые отзывы.

Кто-то узнал об этой особенности, увидев PR, использующие эту функцию в .NET Runtime. Другие команды Microsoft оставляют важные отзывы, используя собственные разработки. Удивительно, что при помощи нового синтаксиса проверки на null мы смогли удалить из .NET Runtime около 20 000 строк.

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

Мы рассмотрели и отклонили глобальную установку проверки на null для всех обнуляемых параметров. Проверка параметров на null заставляет выбирать, как будет обрабатываться null.

Есть много методов, где аргумент null — допустимое значение, а значит, выполнять такую проверку везде, где тип не null, чрезмерно и это скажется на производительности. Ограничить только уязвимые null методы (например, public interface) крайне сложно.

По работе с .NET Runtime мы знаем, что во многих местах проверка не происходит, а значит, потребуется механизм отказа от использования каждого параметра. Пока мы не думаем, что глобальная проверка на null во время выполнения может быть уместной, и если когда-нибудь рассмотрим такую проверку, то это будет другая функция языка.

Резюме

Visual Studio 17.1 и .NET SDK 6.0.200 предлагают взглянуть на ранний C# 11. Поэкспериментировать можно с проверкой параметров на null, шаблонами списка и символами новых строк в фигурных скобках интерполированных строк. Мы будем рады услышать ваше мнение здесь [в комментариях к оригинальной статье] или в дискуссии репозитория CSharpLang на Github.

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

Выбрать другую востребованную профессию:

Краткий каталог курсов и профессий

Data Science и Machine Learning

Python, веб-разработка

Мобильная разработка

Java и C#

От основ — в глубину

А также

Читайте так же: