Новые возможности C# 10

В C# 10 добавлены следующие функции и улучшения языка C#:

Дополнительные функции доступны в режиме предварительной версии. Опробуйте эти функции и отправьте о них отзыв. До окончательного выпуска в них могут быть внесены изменения. Для использования этих функций в проекте необходимо задать <LangVersion> значение Preview. Дополнительные сведения об универсальных атрибутах см. далее в этой статье.

C# 10 поддерживается в .NET 6. Дополнительные сведения см. в статье Управление версиями языка C#.

Вы можете скачать последний пакет SDK для .NET 6 на странице скачиваемых файлов .NET. Также вы можете загрузить предварительную версию Visual Studio 2022, которая включает пакет SDK для .NET 6.

Структуры записей

Вы можете объявлять записи типа значения с помощью объявления record struct``readonly record struct или . Теперь путем объявления record class вы можете уточнить, что record имеет ссылочный тип.

Улучшения типов структуры

В C# 10 реализованы следующие улучшения, связанные с типами структур:

Обработчик интерполированных строк

Можно создать тип, который создает результирующую строку на основе выражения интерполированной строки. Библиотеки .NET используют эту функцию во многих интерфейсах API. Вы можете создать такой обработчик, следуя инструкциям из этого руководства.

Глобальные директивы using

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

Объявление пространства имен в пределах файла

Можно использовать новую форму объявления namespace, чтобы объявить, что все последующие объявления являются членами объявленного пространства имен:

namespace MyNamespace;

Этот новый синтаксис сохраняет как горизонтальное, так и вертикальное пространство для наиболее распространенных объявлений namespace.

Расширенные шаблоны свойств

Начиная с C# 10 можно ссылаться на вложенные свойства или поля в шаблоне свойства. Например, шаблон формы

{ Prop1.Prop2: pattern }

допустим в C# 10 и более поздних версиях и эквивалентен

{ Prop1: { Prop2: pattern } }

допустим в C# 8.0 и более поздних версиях.

Дополнительные сведения см. в примечании к предлагаемой функции Расширенные шаблоны свойств. Дополнительные сведения о шаблоне свойства см. в разделе Шаблон свойства статьи Шаблоны.

Улучшения лямбда-выражений

В C# 10 включено множество улучшений обработки лямбда-выражений:

  • Лямбда-выражения могут иметь естественный тип, где компилятор может вывести тип делегата из лямбда-выражения или группы методов.
  • Лямбда-выражения могут объявлять тип возвращаемого значения, если компилятор не может его вывести.
  • К лямбда-выражениям могут применяться атрибуты.

Эти функции делают лямбда-выражения более похожими на методы и локальные функции. Они упрощают использование лямбда-выражений без объявления переменной типа делегата и более тесно работают с новыми минимальными API ASP.NET Core.

Константные интерполированные строки

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

Типы записей могут запечатывать ToString

В C# 10 можно добавить модификатор sealed при переопределении ToString в типе записи. Запечатывание метода ToString предотвращает синтезирование компилятором метода ToString для любых производных типов записей. Использование ToString с sealed гарантирует, что все производные типы записей используют метод ToString, определенный в общем базовом типе записи. Дополнительные сведения об этой функции см. в статье о записях.

Присваивание и объявление в одном и том же деконструировании

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

// Initialization:
(int x, int y) = point;

// assignment:
int x1 = 0;
int y1 = 0;
(x1, y1) = point;

C# 10 устраняет это ограничение:

int x = 0;
(x, int y) = point;

Улучшенное определенное назначение

До C# 10 существовало много сценариев, в которых при определенном назначении и анализе состояния NULL возникали ложноположительные предупреждения. Обычно они были связаны со сравнениями с логическими константами, доступом к переменной только в операторах true или false в операторе if и выражениях объединения со значением NULL. Эти примеры создавали предупреждения в предыдущих версиях C#, но не в C# 10:

string representation = "N/A";
if ((c != null && c.GetDependentValue(out object obj)) == true)
{
   representation = obj.ToString(); // undesired error
}

// Or, using ?.
if (c?.GetDependentValue(out object obj) == true)
{
   representation = obj.ToString(); // undesired error
}

// Or, using ??
if (c?.GetDependentValue(out object obj) ?? false)
{
   representation = obj.ToString(); // undesired error
}

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

Разрешен атрибут AsyncMethodBuilder в методах

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

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

Диагностика атрибута CallerArgumentExpression

Можно использовать System.Runtime.CompilerServices.CallerArgumentExpressionAttribute, чтобы указать параметр, который компилятор заменяет текстовым представлением другого аргумента. Эта функция позволяет библиотекам создавать более конкретные диагностические сведения. Следующий код проверяет условие. Если условие имеет значение false, сообщение об исключении содержит текстовое представление аргумента, переданного в condition:

public static void Validate(bool condition, [CallerArgumentExpression("condition")] string? message=null)
{
    if (!condition)
    {
        throw new InvalidOperationException($"Argument failed validation: <{message}>");
    }
}

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

Улучшенная прагма #line

C# 10 поддерживает новый формат для прагмы #line. Скорее всего, вы не будете использовать новый формат, но заметите его влияние. Улучшения обеспечивают более детализированные выходные данные в предметно-ориентированных языках (DSL), например Razor. Подсистема Razor использует эти усовершенствования для улучшения процесса отладки. Отладчики смогут точнее выделять источник Razor. Дополнительные сведения о новом синтаксисе см. в статье о директивах препроцессора в справочнике по языку. Также можно прочитать спецификацию функции, чтобы найти примеры для Razor.

Универсальные атрибуты

Важно!

Универсальные атрибуты являются предварительной версией функций. Для ее включения необходимо задать <LangVersion> значение Preview. До окончательного выпуска в эту функцию могут быть внесены изменения.

Можно объявить универсальный класс, базовым классом которого является System.Attribute. При этом вы получите более удобный синтаксис для атрибутов, требующих параметра System.Type. Ранее вам пришлось бы создать атрибут, который принимает Type качестве параметра конструктора:

public class TypeAttribute : Attribute
{
   public TypeAttribute(Type t) => ParamType = t;

   public Type ParamType { get; }
}

А для применения атрибута следовало бы использовать оператор typeof:

[TypeAttribute(typeof(string))] 
public string Method() => default;

С помощью этой новой функции можно создать универсальный атрибут:

public class GenericAttribute<T> : Attribute { }

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

[GenericAttribute<string>()]
public string Method() => default;

И вы можете применить полностью закрытый сконструированный универсальный атрибут. Иными словами, необходимо указать все параметры типа. Например, следующее выражение недопустимо:

public class GenericType<T>
{
   [GenericAttribute<T>()] // Not allowed! generic attributes must be fully closed types.
   public string Method() => default;
}

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

  • dynamic
  • nint, nuint
  • string? (или любой ссылочный тип, допускающий значение NULL)
  • (int X, int Y) (или любые другие типы кортежей, использующие синтаксис кортежей C#).

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

  • object для dynamic.
  • IntPtr вместо nint или unint.
  • string вместо string?.
  • ValueTuple<int, int> вместо (int X, int Y).