Новые возможности C# 9.0What's new in C# 9.0

В C# 9.0 добавлены следующие функции и улучшения языка C#.C# 9.0 adds the following features and enhancements to the C# language:

C# 9.0 поддерживается в .NET 5.C# 9.0 is supported on .NET 5. Дополнительные сведения см. в статье Управление версиями языка C#.For more information, see C# language versioning.

Вы можете скачать последний пакет SDK для .NET на странице скачиваемых файлов .NET.You can download the latest .NET SDK from the .NET downloads page.

Типы записейRecord types

В C# 9.0 появились *типы записей _, которые представляют собой ссылочный тип, предоставляющий синтезированные методы для обеспечения семантики значений для равенства.C# 9.0 introduces *record types _, which are a reference type that provides synthesized methods to provide value semantics for equality. По умолчанию записи являются неизменяемыми.Records are immutable by default.

Типы записей упрощают создание неизменяемых ссылочных типов в .NET.Record types make it easy to create immutable reference types in .NET. Исторически типы .NET в основном классифицируются как ссылочные типы (включая классы и анонимные типы) и типы значений (включая структуры и кортежи).Historically, .NET types are largely classified as reference types (including classes and anonymous types) and value types (including structs and tuples). Хотя рекомендуется использовать неизменяемые типы значений, изменяемые типы значений часто не приводят к ошибке.While immutable value types are recommended, mutable value types don’t often introduce errors. Переменные типа значения содержат значения, поэтому, когда типы значений передаются в методы, изменения вносятся в копию исходных данных.Value type variables hold the values so changes are made to a copy of the original data when value types are passed to methods.

Также существует множество преимуществ для неизменяемых ссылочных типов.There are many advantages to immutable reference types as well. Эти преимущества более выражены в параллельных программах с общими данными.These advantages are more pronounced in concurrent programs with shared data. К сожалению, C# вынуждает писать некоторый дополнительный код для создания неизменяемых ссылочных типов.Unfortunately, C# forced you to write quite a bit of extra code to create immutable reference types. Записи предоставляют объявление типа для неизменяемого ссылочного типа, использующего семантику значения для равенства.Records provide a type declaration for an immutable reference type that uses value semantics for equality. Синтезированные методы для проверки на равенство и хэш-кодов считают две записи равными, если равны их свойства.The synthesized methods for equality and hash codes consider two records equal if their properties are all equal. Рассмотрим следующее определение.Consider this definition:

public record Person
{
    public string LastName { get; }
    public string FirstName { get; }

    public Person(string first, string last) => (FirstName, LastName) = (first, last);
}

Определение записи создает тип Person, который содержит два свойства только для чтения: FirstName и LastName.The record definition creates a Person type that contains two readonly properties: FirstName and LastName. Person является ссылочным типом.The Person type is a reference type. Если взглянуть на IL, это будет класс.If you looked at the IL, it’s a class. Он является неизменяемым в том смысле, что ни одно из свойств нельзя изменить после создания.It’s immutable in that none of the properties can be modified once it's been created. При определении типа записи компилятор синтезирует несколько других методов.When you define a record type, the compiler synthesizes several other methods for you:

  • Методы для определения равенства на основе значенийMethods for value-based equality comparisons
  • Переопределение для GetHashCode()Override for GetHashCode()
  • Копирование и клонирование членовCopy and Clone members
  • PrintMembers и ToString()PrintMembers and ToString()

Записи поддерживают наследование.Records support inheritance. Новую запись, производную от Person, можно объявить следующим образом.You can declare a new record derived from Person as follows:

public record Teacher : Person
{
    public string Subject { get; }

    public Teacher(string first, string last, string sub)
        : base(first, last) => Subject = sub;
}

Можно также запечатывать записи, чтобы предотвратить дальнейшее наследование.You can also seal records to prevent further derivation:

public sealed record Student : Person
{
    public int Level { get; }

    public Student(string first, string last, int level) : base(first, last) => Level = level;
}

Компилятор синтезирует разные версии приведенных выше методов.The compiler synthesizes different versions of the methods above. Сигнатуры методов зависят от того, является ли тип записи запечатанным и является ли прямой базовый класс объектом.The method signatures depend on if the record type is sealed and if the direct base class is object. Записи должны иметь следующие возможности.Records should have the following capabilities:

  • Проверка равенства основана на значении и включает проверку соответствия типов.Equality is value-based, and includes a check that the types match. Например, Student не может быть равно Person, даже если две записи имеют одно и то же имя.For example, a Student can't be equal to a Person, even if the two records share the same name.
  • Записи имеют единообразное строковое представление, создаваемое автоматически.Records have a consistent string representation generated for you.
  • Записи поддерживают создание копий.Records support copy construction. Правильная конструкция копирования должна включать иерархии наследования и свойства, добавленные разработчиками.Correct copy construction must include inheritance hierarchies, and properties added by developers.
  • Записи можно копировать с изменениями.Records can be copied with modification. Операции копирования и изменения поддерживают неразрушающие мутации.These copy and modify operations supports non-destructive mutation.

Помимо знакомых перегрузок Equals, operator == и operator !=, компилятор синтезировал новое свойство EqualityContract.In addition to the familiar Equals overloads, operator ==, and operator !=, the compiler synthesizes a new EqualityContract property. Это свойство возвращает объект Type, соответствующий типу записи.The property returns a Type object that matches the type of the record. Если базовый тип равен object, свойство будет являться virtual.If the base type is object, the property is virtual. Если базовый тип является другим типом записи, свойство будет являться override.If the base type is another record type, the property is an override. Если тип записи равен sealed, свойство будет являться sealed.If the record type is sealed, the property is sealed. Синтезированный метод GetHashCode использует GetHashCode из всех свойств и полей, объявленных в базовом типе и типе записи.The synthesized GetHashCode uses the GetHashCode from all properties and fields declared in the base type and the record type. Эти синтезированные методы применяют равенство на основе значений во всей иерархии наследования.These synthesized methods enforce value-based equality throughout an inheritance hierarchy. Это означает, что Student никогда не будет считаться равным Person с тем же именем.That means a Student will never be considered equal to a Person with the same name. Типы двух записей должны совпадать, а все свойства, общие для типов записей, равны.The types of the two records must match as well as all properties shared among the record types being equal.

Записи также имеют синтезированный конструктор и метод "clone" для создания копий.Records also have a synthesized constructor and a "clone" method for creating copies. Синтезированный конструктор имеет один параметр типа записи.The synthesized constructor has a single parameter of the record type. Он создает новую запись с теми же значениями всех свойств записи.It produces a new record with the same values for all properties of the record. Этот конструктор является закрытым, если запись запечатана, в противном случае он является защищенным.This constructor is private if the record is sealed, otherwise it's protected. Синтезированный метод "clone" поддерживает конструкцию копирования для иерархий записей.The synthesized "clone" method supports copy construction for record hierarchies. Термин "clone" взят в кавычки, поскольку фактическое имя создается компилятором.The term "clone" is in quotes because the actual name is compiler generated. Нельзя создать метод с именем Clone в типе записи.You can't create a method named Clone in a record type. Синтезированный метод "clone" возвращает тип копируемой записи с помощью виртуальной диспетчеризации.The synthesized "clone" method returns the type of record being copied using virtual dispatch. Компилятор добавляет различные модификаторы для метода "clone" в зависимости от модификаторов доступа в record.The compiler adds different modifiers for the "clone" method depending on the access modifiers on the record:

  • Если тип записи является abstract, метод "clone" также является abstract.If the record type is abstract, the "clone" method is also abstract. Если базовый тип не является object, метод также является override.If the base type isn't object, the method is also override.
  • Для типов записей, которые не являются abstract, если базовый тип является objectFor record types that aren't abstract when the base type is object:
    • Если запись является sealed, в метод "clone" не добавляются дополнительные модификаторы (то есть он не virtual).If the record is sealed, no additional modifiers are added to the "clone" method (meaning it is not virtual).
    • Если запись не является sealed, метод "clone" является virtual.If the record isn't sealed, the "clone" method is virtual.
  • Для типов записей, которые не являются abstract, если базовый тип не является objectFor record types that aren't abstract when the base type is not object:
    • Если запись является sealed, метод "clone" также является sealed.If the record is sealed, the "clone" method is also sealed.
    • Если запись не является sealed, метод "clone" является override.If the record isn't sealed, the "clone" method is override.

В результате всех этих правил сравнение на равенство реализуется единообразно в любой иерархии типов записей.The result of all these rules is the equality is implemented consistently across any hierarchy of record types. Две записи равны друг другу, если их свойства равны и их типы совпадают, как показано в следующем примере.Two records are equal to each other if their properties are equal and their types are the same, as shown in the following example:

var person = new Person("Bill", "Wagner");
var student = new Student("Bill", "Wagner", 11);

Console.WriteLine(student == person); // false

Компилятор синтезировал два метода, поддерживающих вывод на печать: переопределение ToString() и PrintMembers.The compiler synthesizes two methods that support printed output: a ToString() override, and PrintMembers. PrintMembers принимает в качестве аргумента System.Text.StringBuilder.The PrintMembers takes a System.Text.StringBuilder as its argument. Он добавляет разделенный запятыми список имен свойств и значений всех свойств в типе записи.It appends a comma-separated list of property names and values for all properties in the record type. PrintMembers вызывает базовую реализацию для всех записей, полученных из других записей.PrintMembers calls the base implementation for any records derived from other records. Переопределение ToString() возвращает строку, полученную PrintMembers, окруженную символами { и }.The ToString() override returns the string produced by PrintMembers, surrounded by { and }. Например, метод ToString() для Student возвращает string, как показано в следующем коде.For example, the ToString() method for Student returns a string like the following code:

"Student { LastName = Wagner, FirstName = Bill, Level = 11 }"

В приведенных ранее примерах для объявления свойств использовался традиционный синтаксис.The examples shown so far use traditional syntax to declare properties. Существует более краткая форма, называемая *позиционной записью*.There's a more concise form called positional records. Ниже приведены три типа записей, определенные ранее в виде позиционной записи.Here are the three record types defined earlier as positional records:

public record Person(string FirstName, string LastName);

public record Teacher(string FirstName, string LastName,
    string Subject)
    : Person(FirstName, LastName);

public sealed record Student(string FirstName,
    string LastName, int Level)
    : Person(FirstName, LastName);

Эти объявления обеспечивают ту же функциональность, что и в предыдущем варианте (с несколькими дополнительными функциями, описанными в следующем разделе).These declarations create the same functionality as the earlier version (with a couple extra features covered in the following section). Эти объявления заканчиваются точкой с запятой вместо квадратных скобок, так как эти записи не добавляют дополнительные методы.These declarations end with a semicolon instead of brackets because these records don't add additional methods. Можно добавить тело и включить в него дополнительные методы.You can add a body, and include any additional methods as well:

public record Pet(string Name)
{
    public void ShredTheFurniture() =>
        Console.WriteLine("Shredding furniture");
}

public record Dog(string Name) : Pet(Name)
{
    public void WagTail() =>
        Console.WriteLine("It's tail wagging time");

    public override string ToString()
    {
        StringBuilder s = new();
        base.PrintMembers(s);
        return $"{s.ToString()} is a dog";
    }
}

Для позиционных записей компилятор создает метод Deconstruct.The compiler produces a Deconstruct method for positional records. Метод Deconstruct имеет параметры, соответствующие именам всех общедоступных свойств в типе записи.The Deconstruct method has parameters that match the names of all public properties in the record type. Метод Deconstruct можно использовать для деконструирования записи в свойства ее компонентов.The Deconstruct method can be used to deconstruct the record into its component properties:

var person = new Person("Bill", "Wagner");

var (first, last) = person;
Console.WriteLine(first);
Console.WriteLine(last);

Наконец, записи поддерживают выражения with.Finally, records support with expressions. Выражение * with _ _ указывает компилятору создать копию записи, но _with (с) — указанными измененными свойствами:A *with expression_ _ instructs the compiler to create a copy of a record, but _with specified properties modified:

Person brother = person with { FirstName = "Paul" };

В приведенной выше строке создается новая запись Person, в которой свойство LastName копируется из person, а свойство FirstName равно "Paul".The previous line creates a new Person record where the LastName property is a copy of person, and the FirstName is "Paul". В выражениях with можно задать любое количество свойств.You can set any number of properties in a with expression. Можно также использовать выражения with для создания точной копии.You can also use with expressions to create an exact copy. Укажите пустой набор свойств для изменения:You specify the empty set for the properties to modify:

Person clone = person with { };

Вы можете написать любые синтезируемые члены, за исключением метода "clone".Any of the synthesized members except the "clone" method may be written by you. Если тип записи имеет метод, совпадающий с сигнатурой какого-либо синтезируемого метода, компилятор будет его синтезировать.If a record type has a method that matches the signature of any synthesized method, the compiler doesn't synthesize that method. Например, в предыдущем примере записи Dog содержится написанный вручную метод ToString().The earlier Dog record example contains a hand coded ToString() method as an example.

Дополнительные сведения о типах записей см. в этом учебнике по исследованию записей.Learn more about record types in this exploration of records tutorial.

Методы задания только инициализацииInit only setters

*Методы задания только для инициализации _ обеспечивают единообразный синтаксис для инициализации членов объекта.*Init only setters _ provide consistent syntax to initialize members of an object. Инициализаторы свойств позволяют ясно понять, какое значение задает то или иное свойство.Property initializers make it clear which value is setting which property. Недостаток заключается в том, что эти свойства должны быть устанавливаемыми.The downside is that those properties must be settable. Начиная с C# 9.0, для свойств и индексаторов можно создавать методы доступа init, а не методы доступа set.Starting with C# 9.0, you can create init accessors instead of set accessors for properties and indexers. Вызывающие объекты могут использовать синтаксис инициализатора свойств для установки этих значений в выражениях создания, но после завершения конструирования эти свойства будут доступны только для чтения.Callers can use property initializer syntax to set these values in creation expressions, but those properties are readonly once construction has completed. Методы задания только для инициализации предоставляют окно для изменения состояния.Init only setters provide a window to change state. Это окно закрывается, когда завершается этап конструирования.That window closes when the construction phase ends. Этап конструирования фактически завершается после всех инициализаций, включая инициализаторы свойств и выражения with.The construction phase effectively ends after all initialization, including property initializers and with-expressions have completed.

Можно объявить методы задания только для инициализации (init) в любом написанном вами типе.You can declare init only setters in any type you write. Ниже приведен пример определения структуры наблюдения за погодой.For example, the following struct defines a weather observation structure:

public struct WeatherObservation
{
    public DateTime RecordedAt { get; init; }
    public decimal TemperatureInCelsius { get; init; }
    public decimal PressureInMillibars { get; init; }

    public override string ToString() =>
        $"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " +
        $"Temp = {TemperatureInCelsius}, with {PressureInMillibars} pressure";
}

Вызывающие объекты могут использовать синтаксис инициализатора свойств для установки значений, сохраняя при этом неизменность.Callers can use property initializer syntax to set the values, while still preserving the immutability:

var now = new WeatherObservation 
{ 
    RecordedAt = DateTime.Now, 
    TemperatureInCelsius = 20, 
    PressureInMillibars = 998.0m 
};

Однако изменение наблюдения после инициализации является ошибкой из-за присвоения свойству только для инициализации значения вне инициализации.But, changing an observation after initialization is an error by assigning to an init-only property outside of initialization:

// Error! CS8852.
now.TemperatureInCelsius = 18;

Методы задания только для инициализации могут быть полезны для задания свойств базового класса из производных классов.Init only setters can be useful to set base class properties from derived classes. Они также могут устанавливать производные свойства через вспомогательные методы в базовом классе.They can also set derived properties through helpers in a base class. В позиционных записях свойства объявляются с помощью методов задания только для инициализации.Positional records declare properties using init only setters. Эти методы задания используются в выражениях with.Those setters are used in with-expressions. Методы задания только для инициализации можно объявить для любых class или struct, которые вы определяете.You can declare init only setters for any class or struct you define.

Инструкции верхнего уровняTop-level statements

*Инструкции верхнего уровня* избавляют от ненужных формальностей во многих приложениях.Top-level statements remove unnecessary ceremony from many applications. Рассмотрим каноническую программу Hello World!.Consider the canonical "Hello World!" .program:

using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Только одна строка кода что-то делает.There’s only one line of code that does anything. С помощью инструкций верхнего уровня можно заменить весь этот шаблон инструкцией using и одной строкой, которая делает всю работу.With top-level statements, you can replace all that boilerplate with the using statement and the single line that does the work:

using System;

Console.WriteLine("Hello World!");

Если требуется однострочная программа, можно удалить директиву using и использовать полное имя типа.If you wanted a one-line program, you could remove the using directive and use the fully qualified type name:

System.Console.WriteLine("Hello World!");

Только один файл в приложении может использовать инструкции верхнего уровня.Only one file in your application may use top-level statements. Если компилятор обнаруживает операторы верхнего уровня в нескольких исходных файлах, это приводит к ошибке.If the compiler finds top-level statements in multiple source files, it’s an error. Ошибка также возникает, если объединить операторы верхнего уровня с объявленным методом точки входа программы (как правило, это метод Main).It’s also an error if you combine top-level statements with a declared program entry point method, typically a Main method. В определенном смысле можно сказать, что один файл содержит инструкции, которые обычно находятся в методе Main класса Program.In a sense, you can think that one file contains the statements that would normally be in the Main method of a Program class.

Одним из наиболее распространенных применений этой функции является создание обучающих материалов.One of the most common uses for this feature is creating teaching materials. Начинающие разработчики C# могут написать каноническую программу Hello World!Beginner C# developers can write the canonical “Hello World!” в одной-двух строках кода.in one or two lines of code. Никакие дополнительные формальности не требуются.None of the extra ceremony is needed. Но и опытные разработчики также найдут много применений для этой функции.However, seasoned developers will find many uses for this feature as well. Инструкции верхнего уровня позволяют экспериментировать в стиле написания сценариев, аналогично записным книжкам Jupyter.Top-level statements enable a script-like experience for experimentation similar to what Jupyter notebooks provide. Инструкции верхнего уровня отлично подходят для небольших консольных и служебных программ.Top-level statements are great for small console programs and utilities. Функции Azure являются идеальным примером использования операторов верхнего уровня.Azure Functions are an ideal use case for top-level statements.

Что важнее всего, инструкции верхнего уровня не ограничивают область применения или сложность приложения.Most importantly, top-level statements don't limit your application’s scope or complexity. Эти инструкции могут обращаться к любому классу .NET и использовать его.Those statements can access or use any .NET class. Они также не ограничивают использование аргументов командной строки и возвращаемых значений.They also don’t limit your use of command-line arguments or return values. Инструкции верхнего уровня могут обращаться к массиву строк с именем args.Top-level statements can access an array of strings named args. Если инструкции верхнего уровня возвращают целочисленное значение, это значение преобразуется в целочисленный код возврата из синтезированного метода Main.If the top-level statements return an integer value, that value becomes the integer return code from a synthesized Main method. Инструкции верхнего уровня могут содержать асинхронные выражения.The top-level statements may contain async expressions. В этом случае синтезированная точка входа возвращает Task или Task<int>.In that case, the synthesized entry point returns a Task, or Task<int>.

Улучшения сопоставления шаблоновPattern matching enhancements

C# 9 включает новые улучшения сопоставления шаблонов.C# 9 includes new pattern matching improvements:

  • *Шаблоны типов* проверяют соответствие переменной определенному типу.Type patterns match a variable is a type
  • *Шаблоны в круглых скобках* усиливают или подчеркивают приоритет сочетаний шаблонов.Parenthesized patterns enforce or emphasize the precedence of pattern combinations
  • *В шаблонах конъюнкций and* требуется соответствие обоих шаблонов.Conjunctive and patterns require both patterns to match
  • *В шаблонах дизъюнкций or* требуется соответствие хотя бы одного из шаблонов.Disjunctive or patterns require either pattern to match
  • *В шаблонах с отрицанием not* требуется несоответствие шаблона.Negated not patterns require that a pattern doesn’t match
  • *В шаблонах сравнения* требуется, чтобы входные данные были меньше, больше, меньше или равны, больше или равны данной константе.Relational patterns require the input be less than, greater than, less than or equal, or greater than or equal to a given constant.

Эти шаблоны обогащают синтаксис шаблонов.These patterns enrich the syntax for patterns. Рассмотрим следующие примеры.Consider these examples:

public static bool IsLetter(this char c) =>
    c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

Либо с помощью дополнительных скобок можно ясно указать, что and имеет более высокий приоритет, чем or.Alternatively, with optional parentheses to make it clear that and has higher precedence than or:

public static bool IsLetterOrSeparator(this char c) =>
    c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';

Одним из наиболее распространенных применений нового синтаксиса является проверка значения на null.One of the most common uses is a new syntax for a null check:

if (e is not null)
{
    // ...
}

Любой из этих шаблонов можно использовать в любом контексте, где разрешены шаблоны: выражения с шаблоном is, выражения switch, вложенные шаблоны и шаблоны метки case оператора switch.Any of these patterns can be used in any context where patterns are allowed: is pattern expressions, switch expressions, nested patterns, and the pattern of a switch statement’s case label.

Производительность и взаимодействиеPerformance and interop

Три новых функции улучшают поддержку собственного взаимодействия и низкоуровневых библиотек, требующих высокой производительности: целые числа собственного размера, указатели функций и пропуск флага localsinit.Three new features improve support for native interop and low-level libraries that require high performance: native sized integers, function pointers, and omitting the localsinit flag.

Целые числа собственного размера nint и nuint являются целочисленными типами.Native sized integers, nint and nuint, are integer types. Они выражаются базовыми типами System.IntPtr и System.UIntPtr.They're expressed by the underlying types System.IntPtr and System.UIntPtr. Компилятор предоставляет дополнительные преобразования и операции для этих типов в качестве собственных целых чисел.The compiler surfaces additional conversions and operations for these types as native ints. Целые числа собственного размера определяют свойства для MaxValue или MinValue.Native sized integers define properties for MaxValue or MinValue. Такие значения не могут быть выражены как константы времени компиляции, так как они зависят от собственного размера целого числа на целевом компьютере.These values can't be expressed as compile time constants because they depend on the native size of an integer on the target machine. Во время выполнения эти значения доступны только для чтения.Those values are readonly at runtime. Для nint можно использовать значения констант в диапазоне [int.MinValue ..You can use constant values for nint in the range [int.MinValue .. int.MaxValue].int.MaxValue]. Для nuint можно использовать значения констант в диапазоне [uint.MinValue ..You can use constant values for nuint in the range [uint.MinValue .. uint.MaxValue].uint.MaxValue]. Компилятор выполняет сворачивание константы для всех унарных и бинарных операторов, используя типы System.Int32 и System.UInt32.The compiler performs constant folding for all unary and binary operators using the System.Int32 and System.UInt32 types. Если результат не помещается в 32 бит, операция выполняется во время выполнения и не считается константой.If the result doesn't fit in 32 bits, the operation is executed at runtime and isn't considered a constant. Целые числа собственного размера могут повысить производительность в сценариях с большим количеством целочисленных вычислений, в которых необходимо обеспечить максимально высокую производительность.Native sized integers can increase performance in scenarios where integer math is used extensively and needs to have the fastest performance possible.

Указатели функций предоставляют простой синтаксис для доступа к кодам операций IL ldftn и calli.Function pointers provide an easy syntax to access the IL opcodes ldftn and calli. Указатели функций можно объявлять с помощью нового синтаксиса delegate_.You can declare function pointers using new delegate_ syntax. Тип delegate* — это тип указателя.A delegate* type is a pointer type. При вызове типа delegate* используется calli, в отличие от делегата, который использует callvirt в методе Invoke().Invoking the delegate* type uses calli, in contrast to a delegate that uses callvirt on the Invoke() method. Синтаксически вызовы являются идентичными.Syntactically, the invocations are identical. При вызове указателя функции используется соглашение о вызовах managed.Function pointer invocation uses the managed calling convention. Если требуется объявить о соглашении о вызовах unmanaged, добавьте ключевое слово unmanaged после синтаксиса delegate*.You add the unmanaged keyword after the delegate* syntax to declare that you want the unmanaged calling convention. Другие соглашения о вызовах можно указать с помощью атрибутов в объявлении delegate*.Other calling conventions can be specified using attributes on the delegate* declaration.

Наконец, можно добавить атрибут System.Runtime.CompilerServices.SkipLocalsInitAttribute, чтобы компилятор не создавал флаг localsinit.Finally, you can add the System.Runtime.CompilerServices.SkipLocalsInitAttribute to instruct the compiler not to emit the localsinit flag. Этот флаг указывает среде CLR на нулевую инициализацию всех локальных переменных.This flag instructs the CLR to zero-initialize all local variables. Флаг localsinit используется в C# по умолчанию, начиная с версии 1.0.The localsinit flag has been the default behavior for C# since 1.0. Однако при использовании дополнительной нулевой инициализации в некоторых сценариях может снизиться производительность.However, the extra zero-initialization may have measurable performance impact in some scenarios. В частности, при использовании stackalloc.In particular, when you use stackalloc. В таких случаях можно добавить атрибут SkipLocalsInitAttribute.In those cases, you can add the SkipLocalsInitAttribute. Его можно добавить в один метод или свойство, в class, struct, interface или даже в модуль.You may add it to a single method or property, or to a class, struct, interface, or even a module. Этот атрибут не влияет на методы abstract. Он влияет на код, созданный для реализации.This attribute doesn't affect abstract methods; it affects the code generated for the implementation.

Эти функции могут повысить производительность в некоторых сценариях.These features can improve performance in some scenarios. Их следует использовать только после тщательного тестирования как до, так и после внедрения.They should be used only after careful benchmarking both before and after adoption. Код, включающий целые числа собственного размера, нужно тестировать на нескольких целевых платформах с различными размерами целых чисел.Code involving native sized integers must be tested on multiple target platforms with different integer sizes. Другие функции требуют небезопасный код.The other features require unsafe code.

Функции подбора и завершенияFit and finish features

Многие другие функции позволяют более эффективно писать код.Many of the other features help you write code more efficiently. В C# 9.0 можно опустить тип в выражении new, если тип созданного объекта уже известен.In C# 9.0, you can omit the type in a new expression when the created object's type is already known. Наиболее часто это используется в объявлениях полей.The most common use is in field declarations:

private List<WeatherObservation> _observations = new();

new с целевым типом можно также использовать, если необходимо создать объект для передачи его в качестве аргумента в метод.Target-typed new can also be used when you need to create a new object to pass as an argument to a method. Рассмотрим метод ForecastFor() со следующей сигнатурой.Consider a ForecastFor() method with the following signature:

public WeatherForecast ForecastFor(DateTime forecastDate, WeatherForecastOptions options)

Его можно вызвать следующим образом.You could call it as follows:

var forecast = station.ForecastFor(DateTime.Now.AddDays(2), new());

Еще один полезный способ использовать эту функцию — объединить ее со свойствами только для инициализации при инициализации нового объекта.Another nice use for this feature is to combine it with init only properties to initialize a new object:

WeatherStation station = new() { Location = "Seattle, WA" };

Экземпляр, созданный конструктором по умолчанию, можно вернуть с помощью инструкции return new();.You can return an instance created by the default constructor using a return new(); statement.

Аналогичная функция улучшает разрешение целевого типа в условных выражениях.A similar feature improves the target type resolution of conditional expressions. Благодаря такому изменению два выражения необязательно должны иметь неявное преобразование из одного в другое, но оба могут иметь неявные преобразования в целевой тип.With this change, the two expressions need not have an implicit conversion from one to the other, but may both have implicit conversions to a target type. Скорее всего, вы не заметите этого изменения.You likely won’t notice this change. Обратите внимание, что некоторые условные выражения, которые ранее требовали приведения или не компилировались, теперь просто начнут работать.What you will notice is that some conditional expressions that previously required casts or wouldn’t compile now just work.

Начиная с C# 9.0, можно добавлять модификатор static в лямбда-выражения или анонимные методы.Starting in C# 9.0, you can add the static modifier to lambda expressions or anonymous methods. Статические лямбда-выражения аналогичны локальным функциям с модификатором static: статические лямбда-выражения и анонимные методы не могут захватывать локальные переменные и состояние экземпляра.Static lambda expressions are analogous to the static local functions: a static lambda or anonymous method can't capture local variables or instance state. Модификатор static предотвращает случайное захватывание других переменных.The static modifier prevents accidentally capturing other variables.

Ковариантные возвращаемые типы обеспечивают гибкость для типов возвращаемых значений методов переопределения.Covariant return types provide flexibility for the return types of override methods. Метод переопределения может возвращать тип, производный от типа возвращаемых значений переопределенного базового метода.An override method can return a type derived from the return type of the overridden base method. Это может быть полезно для записей и для других типов, которые поддерживают виртуальные методы клонирования или фабричные методы.This can be useful for records and for other types that support virtual clone or factory methods.

Кроме того, цикл foreach будет распознавать и использовать метод расширения GetEnumerator, который в противном случае удовлетворяет шаблону foreach.In addition, the foreach loop will recognize and use an extension method GetEnumerator that otherwise satisfies the foreach pattern. Это изменение означает, что foreach согласуется с другими конструкциями на основе шаблонов, такими как асинхронная модель и деконструирование на основе шаблона.This change means foreach is consistent with other pattern-based constructions such as the async pattern, and pattern-based deconstruction. На практике это изменение означает, что можно добавить поддержку foreach в любой тип.In practice, this change means you can add foreach support to any type. При перечислении объектов имеет смысл ограничить его использование.You should limit its use to when enumerating an object makes sense in your design.

Кроме того, можно использовать пустые переменные в качестве параметров для лямбда-выражений.Next, you can use discards as parameters to lambda expressions. Это удобное изменение позволяет избежать присвоения имени аргументу, а компилятор может избежать его использования.This convenience enables you to avoid naming the argument, and the compiler may avoid using it. Для любого аргумента используется символ _.You use the _ for any argument. Дополнительные сведения см. в разделе Входные параметры лямбда-выражения статьи о лямбда-выражениях.For more information, see the Input parameters of a lambda expression section of the Lambda expressions article.

Наконец, теперь можно применять атрибуты к локальным функциям.Finally, you can now apply attributes to local functions. Например, к локальным функциям можно применить заметки атрибутов, допускающих значение null.For example, you can apply nullable attribute annotations to local functions.

Поддержка генераторов кодаSupport for code generators

Две заключительные функции обеспечивают поддержку генераторов кода в C#.Two final features support C# code generators. Генераторы кода C# — это компонент, который вы можете написать подобно анализатору или исправлению кода Roslyn.C# code generators are a component you can write that is similar to a roslyn analyzer or code fix. Разница заключается в том, что генераторы кода анализируют код и пишут новые файлы исходного кода в рамках процесса компиляции.The difference is that code generators analyze code and write new source code files as part of the compilation process. Типичный генератор кода ищет в коде атрибуты или другие соглашения.A typical code generator searches code for attributes or other conventions.

Генератор кода считывает атрибуты или другие элементы кода с помощью анализирующих интерфейсов API Roslyn.A code generator reads attributes or other code elements using the Roslyn analysis APIs. На основе этой информации он добавляет новый код в компиляцию.From that information, it adds new code to the compilation. Генераторы исходного кода могут только добавлять код, они не могут изменять существующий код в компиляции.Source generators can only add code; they aren't allowed to modify any existing code in the compilation.

Этими двумя функциями, добавленными для поддержки генераторов кода, являются расширения для синтаксиса разделяемого метода _ и _инициализаторов модулей*.The two features added for code generators are extensions to partial method syntax _, and _module initializers*. Сначала рассмотрим изменения в разделяемые методы.First, the changes to partial methods. До C# 9.0 разделяемые методы были private, но не могли иметь модификаторов доступа, иметь возвращаемое значение void и параметры out.Before C# 9.0, partial methods are private but can't specify an access modifier, have a void return, and can't have out parameters. Эти ограничения подразумевают, что, если реализация метода не предоставлена, компилятор удаляет все вызовы к разделяемому методу.These restrictions meant that if no method implementation is provided, the compiler removes all calls to the partial method. В C# 9.0 эти ограничения снимаются, но требуется, чтобы объявления разделяемых методов имели реализацию.C# 9.0 removes these restrictions, but requires that partial method declarations have an implementation. Генераторы кода могут предоставить такую реализацию.Code generators can provide that implementation. Чтобы избежать критических изменений, компилятор рассматривает любой разделяемый метод без модификатора доступа как метод, следующий старым правилам.To avoid introducing a breaking change, the compiler considers any partial method without an access modifier to follow the old rules. Если разделяемый метод включает модификатор доступа private, этот разделяемый метод обрабатывается в соответствии с новыми правилами.If the partial method includes the private access modifier, the new rules govern that partial method.

Второй новой функцией для генераторов кода являются _*инициализаторы модулей**.The second new feature for code generators is _*module initializers**. Инициализаторы модулей — это методы, к которым прикреплен атрибут ModuleInitializerAttribute.Module initializers are methods that have the ModuleInitializerAttribute attribute attached to them. Эти методы будут вызываться средой выполнения до доступа к полю или вызова метода в целом модуле.These methods will be called by the runtime before any other field access or method invocation within the entire module. Метод инициализатора модуля:A module initializer method:

  • должен быть статическим;Must be static
  • должен быть без параметров;Must be parameterless
  • должен возвращать значение void;Must return void
  • не должен быть универсальным методом;Must not be a generic method
  • не должен содержаться в универсальном классе;Must not be contained in a generic class
  • должен быть доступен из содержащего модуля.Must be accessible from the containing module

Последний пункт фактически означает, что метод и содержащий его класс должны быть внутренними или открытыми.That last bullet point effectively means the method and its containing class must be internal or public. Метод не может быть локальной функцией.The method can't be a local function.