Что полезного в новых версиях C#

Posted by

С# продолжает развиваться, и в новых версиях команда разработчиков языка регулярно добавляет в него новые функции и возможности. Вместе с экспертами Ozon Tech и Route 256 подготовили обзор полезных и неочевидных фич, которые появились в языке за последние четыре года. Это не тот C#, который вы учили пять лет назад.

История версий C#
Операторы верхнего уровня — программы без Main методов
Nullable-типы
Типы записи и записи структуры
Индексы и диапазоны
Паттерн-матчинг

История версий C#

!

C# был придуман в Microsoft под руководством Андерса Хейлсберга, который до этого работал над языком Delphi. Язык создавался как конкурент Java со свойствами и событиями.

Первая версия C# увидела свет в 2002 году. Затем каждые два или три года Microsoft выпускал новую версию языка. В C# постепенно добавили обобщённые типы и итераторы, Linq, фичи функциональных языков программирования, ключевое слово dynamic для упрощения работы с COM, интеграции с динамическими языками на платформе .NET, asyncawait, кортежи и другое.

!

Начиная с C# 8, разработчики выпускают новую версию языка ежегодно. В них уже нет масштабных изменений, вроде введения Linq или asyncawait, а некоторые фичи, такие как паттерн-матчинг, развиваются от релиза к релизу. К сожалению, это приводит к тому, что на момент появления многие фичи просто не используются. Программисты, которые изучали C# по книжкам и статьям пятилетней давности, даже не подозревают об их существовании.

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

Операторы верхнего уровня — программы без Main методов

Все языки, унаследовавшие синтаксис от языка C, страдают многословностью. C# не стал исключением и перенял у Java необходимость писать следующим образом:

В C# 9 появилась фича Top-level statements. Теперь тот же самый код возможно переписать короче:

Более того, используя implicit usings, using System писать необязательно. Компилятор автоматически добавляет юзинги для популярных пространств имён. Начиная с .NET 6 и C# 10 новое консольное приложение выглядит так:

Если вам не подходит автоматика, то можете глобально задать юзинги в отдельном .cs файле или в файле проекта.

Код, написанный таким образом, «под капотом» заворачивается в функцию Main. Все функции, которые вы определите, станут локальными и смогут использовать переменные, определённые ранее. Определять типы можно в том же файле, но только после всех top-level statements.

Nullable-типы

В C# 8 появилась фича — Nullable Reference Types, аналогичная Nullable Value Types. В проектах, созданных до .NET 6, фича была отключена по умолчанию. Она работает по следующим правилам:

— Если есть переменная или параметр ссылочного типа T, то ему необходимо присвоить значение, не являющееся null (non-nullable).
— Чтобы присвоить null, нужно поставить знак вопроса после имени типа — T? (nullable).
— Чтобы привести T? к T, необходимо использовать оператор ! после nullable выражения, иначе компилятор выдает ошибку.
— При обращении к членам T? компилятор статически проверяет, что значение ссылки не равно null.

В отличие от Nullable Value Types, string и string? — не разные типы. Это один и тот же тип string, подсказка компилятору и набор атрибутов, помогающих проводить статический анализ.

Как следствие, Nullable Reference Types не даёт гарантий за пределами вашего кода. Если в коде функция публичного интерфейса класса принимает string, а не string?, то вам необходимо написать проверку на null, хоть она и выглядит избыточной.

Компилятор, в свою очередь, при нарушении правил Nullable Reference Types генерирует предупреждения, а не ошибки. Но это легко исправить, указав в свойствах проекта, что Nullable предупреждения стоит воспринимать как ошибки.

!

Важная вещь, которую стоит помнить, — оператор var всегда создаёт переменную nullable reference типа.

Типы записи и записи структуры в C#

Как появились

Анонимные типы добавили ещё в третью версию C#. Они неизменяемые, имеют структурную, а не ссылочную эквивалентность, и удобное представление в ToString().

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

Такие типы можно создать и «руками», но вам быстро надоест каждый раз переопределять EqualsGetHashCode и ToString, и вы начнёте искать более простые способы.

Один из них — кортежи, которые появились в C# 7.0. Кортежи можно красиво использовать в варианте с именами полей:

Кортежи тоже имеют структурную эквивалентность и удобное представление в ToString(). Но в отличие от анонимных типов кортежи изменяемые.

Однако, как и Nullable Reference Types, имена кортежей — магия компилятора. «Под капотом» там всегда System.ValueTuple, поля Item1..ItemN, и  никакие интерфейсы реализовать не получится.

Как создавать

Начиная с C# 8, можно создавать свои типы, которые объединяют фичи кортежей и анонимных типов, но при этом могут иметь методы и реализовывать интерфейсы. Такие типы называются записями, а для их создания используется ключевое слово record.

Тип, объявленный как record, автоматически реализует деконструктор (не путать с деструктором), Equals, GetHashCode, ToString, а также интерфейс IEquatable и перегружает операторы сравнения. Свойства FirstName и LastName будут неизменяемыми. В остальном это обычный класс. Он может содержать методы, наследовать другие классы, реализовывать интерфейсы.

В примере выше используется фича из C# 9 — Target-typed new operator, когда после new не нужно использовать имя типа, если выражение присваивается переменной или параметру этого типа. Это облегчает использование Nullable Reference Types.

Начиная с C# 11 можно создать тип запись, которая является полным эквивалентом анонимного класса:

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

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

Записи структуры

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

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

Оператор with

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

Оператор with работает не только с типами записями, но и с readonly структурами и анонимными типами.

Индексы и диапазоны в C#

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

Аналогично можно обратиться ко второму элементу с конца: ^2, к третьему: ^3 и так далее.

Кроме того, появилась возможность извлекать подмассив из массива с помощью индексов, как в языках F#, Python и многих других:

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

Под капотом оператор диапазона превращается в структуру System.Range:

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

Большинство типов-коллекций автоматически получит поддержку индексов, а для диапазонов нужно будет реализовать метод Slice. Кроме того, вы можете определить свои свойства и методы, которые работают со структурами System.Range и System.Index.

Паттерн-матчинг

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

is на стероидах

Оператор is существует с первой версии языка C# и используется для проверки переменной на соответствие определённому типу. До сих пор в статьях и книгах по C# можно встретить такой код:

В современном C# нет необходимости проверять тип и приводить к нему в два действия. Можно просто использовать паттерн-матчинг:

Более того, в C# 11 в паттерн-матчинг можно внести вырезание строки:

Как это читать: выражение после is говорит, что переменная x должна быть строкой (паттерн типа) и может быть разложена на три подстроки (паттерн списка):

— один символ в начале, который мы в дальнейшем никак не используем, так как указали подчёркивание вместо имени переменной (паттерн отбрасывания значения);

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

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

В этом случае, если строка содержит меньше двух символов, то выражение is вычисляется как false и Console.WriteLine не будет выполнено. Если нам хочется, чтобы подстрока s имела длину больше нуля, то необходимо написать ещё одно условие:

Теперь мы проверяем, чтобы переменная s, полученная в результате сопоставления с шаблоном, соответствовала паттерну (паттерн свойства): свойство Length должно иметь значение больше 0 (паттерн сравнения). Здесь важно не путать логические паттерны и булевы операторы. Это не одно и то же, и они не взаимозаменяемы.

То же самое можно записать в виде логического паттерна внутри лист-паттерна:

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

switch и паттерн-матчинг

В выражениях case оператора switсh также можно использовать паттерны:

В примере выше используются паттерн деконструкции (в документации «позиционный паттерн») и паттерны сравнения.

В C# 8 ввели switch expression с более компактным синтаксисом, похожим на функциональные языки:

В этом и предыдущем примере используется case guard — произвольное булево выражение после ключевого слова when. Оно необходимо в случаях, когда мы не можем выразить необходимые условия в паттернах.

switch expression удобно использовать для создания машины состояний. На одном из форумов была такая задача: для последовательности цифр требуется убедиться, что 1 есть как минимум один раз, 2 — только один раз, 3 — как минимум один раз, а после этого ничего быть не должно.

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

Проверка на null

После появления паттерн-матчинга проверку на равенство ссылки null необходимо проделывать следующим образом:

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

Заключение

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

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

Подсказка: А тем, кому хочется большего, рекомендуем курс  Route 256 от Ozon Tech по современным технологиям C#.

Программа рассчитана на разработчиков с опытом от 3 лет. Преподаватели и тьюторы — инженеры Ozon Tech. За два месяца вы сможете подтянуть скилы и освежить знания. На курсе вы научитесь создавать и настраивать микросервисы на ASP.NET Core, эффективно работать с асинхронным кодом, проектировать сложные распределенные системы, создавать REST и gRPC API и другое.

Надеемся, статься оказалась для вас полезной. Остались вопросы? Задайте их в комментариях.

Реклама ООО «Озон технологии» LjN8K484i

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *