Новые возможности в C# версий 7.0–7.3What's new in C# 7.0 through C# 7.3

В C# версий 7.0–7.3 был реализован ряд функций и улучшены возможности разработки на C#.C# 7.0 through C# 7.3 brought a number of features and incremental improvements to your development experience with C#. В этой статье приводятся общие сведения о новых функциях языка и параметрах компилятора.This article provides an overview of the new language features and compiler options. Приводится описание поведения C# 7.3, то есть самой последней версии, поддерживаемой для приложений на основе .NET Framework.The descriptions describe the behavior for C# 7.3, which is the most recent version supported for .NET Framework-based applications.

В C# 7.1 добавлен элемент конфигурации выбор версии языка, что позволяет указать версию языка компилятора в файле проекта.The language version selection configuration element was added with C# 7.1, which enables you to specify the compiler language version in your project file.

В C# версий 7.0–7.3 добавлены следующие функции и темы в язык C#:C# 7.0-7.3 adds these features and themes to the C# language:

  • Кортежи и пустые переменныеTuples and discards
    • Вы можете создать простые, неименованные типы, содержащие несколько открытых полей.You can create lightweight, unnamed types that contain multiple public fields. Компиляторы и инструменты IDE понимают семантику этих типов.Compilers and IDE tools understand the semantics of these types.
    • Пустые переменные представляют собой временные переменные, доступные только для записи, которые используются при присвоении в тех случаях, когда присваиваемое значение не важно.Discards are temporary, write-only variables used in assignments when you don't care about the value assigned. Они особенно полезны при деконструкции кортежей и пользовательских типов, а также при вызове методов с параметрами out.They're most useful when deconstructing tuples and user-defined types, as well as when calling methods with out parameters.
  • Соответствие шаблонуPattern Matching
    • На основе произвольных типов и значений их членов можно создать логику ветвления.You can create branching logic based on arbitrary types and values of the members of those types.
  • Метод async Mainasync Main method
    • Точка входа для приложения может иметь модификатор async.The entry point for an application can have the async modifier.
  • Локальные функцииLocal Functions
    • Функции можно вкладывать в другие функции, чтобы ограничить область их действия и видимость.You can nest functions inside other functions to limit their scope and visibility.
  • Другие элементы, воплощающие выражениеMore expression-bodied members
    • Список элементов, которые можно создавать с помощью выражений, увеличился.The list of members that can be authored using expressions has grown.
  • Выражения throwthrow Expressions
    • Исключения могут возникать в конструкциях кода, которые ранее не допускались, поскольку throw был оператором.You can throw exceptions in code constructs that previously weren't allowed because throw was a statement.
  • Литеральные выражения defaultdefault literal expressions
    • Литеральные выражения по умолчанию можно использовать в выражениях значения по умолчанию, если можно вывести тип целевого объекта.You can use default literal expressions in default value expressions when the target type can be inferred.
  • Усовершенствования в синтаксисе числовых литераловNumeric literal syntax improvements
    • Новые маркеры делают числовые константы более удобочитаемыми.New tokens improve readability for numeric constants.
  • Переменные outout variables
    • Значения out можно объявлять встроенными как аргументы для метода, в котором они используются.You can declare out values inline as arguments to the method where they're used.
  • Неконечные именованные аргументыNon-trailing named arguments
    • После именованных аргументов могут следовать позиционные аргументы.Named arguments can be followed by positional arguments.
  • Модификатор доступа private protectedprivate protected access modifier
    • Модификатор доступа private protected разрешает доступ для производных классов в одной сборке.The private protected access modifier enables access for derived classes in the same assembly.
  • Улучшенное разрешение перегрузкиImproved overload resolution
    • Новые правила для устранения неоднозначности разрешения перегрузки.New rules to resolve overload resolution ambiguity.
  • Методы написания безопасного и эффективного кодаTechniques for writing safe efficient code
    • Ряд улучшений синтаксиса, обеспечивающих работу с типами значений с использованием семантики ссылок.A combination of syntax improvements that enable working with value types using reference semantics.

Наконец, компилятор получил новые параметры:Finally, the compiler has new options:

  • -refout и -refonly, которые управляют созданием базовой сборки.-refout and -refonly that control reference assembly generation.
  • -publicsign позволяет включить подписывание сборок как программного обеспечения с открытым кодом;-publicsign to enable Open Source Software (OSS) signing of assemblies.
  • -pathmap позволяет предоставить сопоставление для исходных каталогов.-pathmap to provide a mapping for source directories.

В оставшейся части этой статьи представлены общие сведения об этих функциях.The remainder of this article provides an overview of each feature. Каждая функция сопровождается обоснованием и описанием синтаксиса.For each feature, you'll learn the reasoning behind it and the syntax. Эти функции можно изучить в своей среде с помощью глобального средства dotnet try:You can explore these features in your environment using the dotnet try global tool:

  1. Установите глобальное средство dotnet-try.Install the dotnet-try global tool.
  2. Клонируйте репозиторий dotnet/try-samples.Clone the dotnet/try-samples repository.
  3. Для репозитория try-samples установите в качестве текущего каталога подкаталог csharp7.Set the current directory to the csharp7 subdirectory for the try-samples repository.
  4. Запустите dotnet try.Run dotnet try.

Кортежи и пустые переменныеTuples and discards

C# предоставляет расширенный синтаксис для классов и структур, который используется для объяснения цели проекта.C# provides a rich syntax for classes and structs that is used to explain your design intent. Однако в некоторых случаях расширенный синтаксис требует дополнительной работы с минимальной результативностью.But sometimes that rich syntax requires extra work with minimal benefit. Зачастую требуется написание методов, которым нужна простая структура, состоящая из более чем одного элемента данных.You may often write methods that need a simple structure containing more than one data element. Для поддержки этих сценариев в C# были добавлены кортежи.To support these scenarios tuples were added to C#. Кортежи — это упрощенные структуры данных, содержащие несколько полей для представления элементов данных.Tuples are lightweight data structures that contain multiple fields to represent the data members. Поля не проверяются, и собственные методы определять нельзя.The fields aren't validated, and you can't define your own methods. Типы кортежей в C# поддерживают == и !=.C# tuple types support == and !=. Дополнительные сведения см. в записи блогаFor more information.

Примечание

Кортежи существовали и в версиях C#, предшествовавших версии 7.0, но были неэффективны и не имели языковой поддержки.Tuples were available before C# 7.0, but they were inefficient and had no language support. Это означает, что ссылки на элементы кортежа можно было задавать только в виде Item1, Item2 и т. д.This meant that tuple elements could only be referenced as Item1, Item2 and so on. В C# 7.0 реализуется языковая поддержка кортежей, что позволяет работать с семантическими именами полей кортежа с использованием новых, более эффективных типов кортежей.C# 7.0 introduces language support for tuples, which enables semantic names for the fields of a tuple using new, more efficient tuple types.

Можно создать кортеж путем присваивания значения каждого элемента, а также (необязательно) задать семантические имена для каждого из элементов кортежа:You can create a tuple by assigning a value to each member, and optionally providing semantic names to each of the members of the tuple:

(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");

Кортеж namedLetters содержит поля, которые называются Alpha и Beta.The namedLetters tuple contains fields referred to as Alpha and Beta. Эти имена существуют только во время компиляции и не сохраняются, например при проверке кортежа посредством отражения во время выполнения.Those names exist only at compile time and aren't preserved, for example when inspecting the tuple using reflection at run time.

В назначении кортежа можно также указать имена полей в правой части назначения:In a tuple assignment, you can also specify the names of the fields on the right-hand side of the assignment:

var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");

В некоторых случаях элементы возвращаемого методом кортежа необходимо распаковать.There may be times when you want to unpackage the members of a tuple that were returned from a method. С этой целью для каждого значения в этом кортеже объявляется отдельная переменная.You can do that by declaring separate variables for each of the values in the tuple. Такая распаковка называется деконструкцией кортежа:This unpackaging is called deconstructing the tuple:

(int max, int min) = Range(numbers);
Console.WriteLine(max);
Console.WriteLine(min);

Аналогичную деконструкцию можно обеспечить для любого типа в .NET.You can also provide a similar deconstruction for any type in .NET. Можно написать метод Deconstruct в качестве члена класса.You write a Deconstruct method as a member of the class. Метод Deconstruct предоставляет набор аргументов out для каждого из свойств, которые нужно извлечь.That Deconstruct method provides a set of out arguments for each of the properties you want to extract. Рассмотрим этот класс Point, предоставляющий метод deconstructor, который извлекает координаты X и Y:Consider this Point class that provides a deconstructor method that extracts the X and Y coordinates:

public class Point
{
    public Point(double x, double y)
        => (X, Y) = (x, y);

    public double X { get; }
    public double Y { get; }

    public void Deconstruct(out double x, out double y) =>
        (x, y) = (X, Y);
}

Отдельные поля можно извлекать, назначая кортежу метод Point:You can extract the individual fields by assigning a Point to a tuple:

var p = new Point(3.14, 2.71);
(double X, double Y) = p;

Очень часто при инициализации кортежа переменные в правой части задания совпадают с именами для элементов кортежа: Имена элементов кортежа можно вывести из переменных, используемых для инициализации кортежа:Many times when you initialize a tuple, the variables used for the right side of the assignment are the same as the names you'd like for the tuple elements: The names of tuple elements can be inferred from the variables used to initialize the tuple:

int count = 5;
string label = "Colors used in the map";
var pair = (count, label); // element names are "count" and "label"

Дополнительные сведения об этой функции см. в статье Типы кортежей.You can learn more about this feature in the Tuple types article.

При деконструкции кортежа или вызове метода с параметрами out часто требуется определить переменную, которую вы не планируете использовать и значение которой не важно.Often when deconstructing a tuple or calling a method with out parameters, you're forced to define a variable whose value you don't care about and don't intend to use. Для работы в таких сценариях в C# реализована поддержка пустых переменных.C# adds support for discards to handle this scenario. Пустая переменная представляет собой доступную только для записи переменную с именем _ (знак подчеркивания). Вы можете назначить одной переменной все значения, которые не потребуются в дальнейшем.A discard is a write-only variable whose name is _ (the underscore character); you can assign all of the values that you intend to discard to the single variable. Пустая переменная является аналогом неприсвоенной переменной и не может использоваться в коде где-либо, за исключением оператора присваивания.A discard is like an unassigned variable; apart from the assignment statement, the discard can't be used in code.

Пустые переменные поддерживается в следующих случаях.Discards are supported in the following scenarios:

  • При деконструкции кортежей или пользовательских типов.When deconstructing tuples or user-defined types.
  • При вызове методов с параметрами out.When calling methods with out parameters.
  • В операции сопоставления шаблонов с выражениями is и switch.In a pattern matching operation with the is and switch statements.
  • В качестве автономного идентификатора в тех случаях, когда требуется явно идентифицировать значение присваивания как пустую переменную.As a standalone identifier when you want to explicitly identify the value of an assignment as a discard.

В приведенном ниже примере определяется метод QueryCityDataForYears, который возвращает кортеж из 6 элементов, содержащий данные по городу за два разных года.The following example defines a QueryCityDataForYears method that returns a 6-tuple that contains data for a city for two different years. В вызове метода в этом примере учитываются только два возвращаемых методом значения population, поэтому при деконструкции кортежа оставшиеся значения обрабатываются как пустые переменные.The method call in the example is concerned only with the two population values returned by the method and so treats the remaining values in the tuple as discards when it deconstructs the tuple.

using System;
using System.Collections.Generic;

public class Example
{
    public static void Main()
    {
        var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

        Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
    }

    private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
    {
        int population1 = 0, population2 = 0;
        double area = 0;

        if (name == "New York City")
        {
            area = 468.48;
            if (year1 == 1960)
            {
                population1 = 7781984;
            }
            if (year2 == 2010)
            {
                population2 = 8175133;
            }
            return (name, area, year1, population1, year2, population2);
        }

        return ("", 0, 0, 0, 0, 0);
    }
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149

Дополнительные сведения см. в разделе Пустые переменные.For more information, see Discards.

Регулярные выраженияPattern matching

Сопоставление шаблонов — это набор функций, которые позволяют использовать новые способы выражения потока управления в коде.Pattern matching is a set of features that enable new ways to express control flow in your code. Можно проверить переменные на их тип, значение или значения их свойств.You can test variables for their type, values or the values of their properties. Эти методы создают более удобочитаемый поток кода.These techniques create more readable code flow.

Сопоставление шаблонов поддерживает выражения is и switch.Pattern matching supports is expressions and switch expressions. Каждое из них позволяет проверять объект и его свойства и определять, соответствует ли этот объект искомому шаблону.Each enables inspecting an object and its properties to determine if that object satisfies the sought pattern. Для добавления правил в шаблон используется ключевое слово when.You use the when keyword to specify additional rules to the pattern.

Выражение шаблона is позволяет использовать знакомый оператор is для запроса объекта о типе и присваивания результата в одной инструкции.The is pattern expression extends the familiar is operator to query an object about its type and assign the result in one instruction. Следующий код проверяет, является ли переменная int; если да, добавляет ее к текущей сумме:The following code checks if a variable is an int, and if so, adds it to the current sum:

if (input is int count)
    sum += count;

Предыдущий небольшой пример показывает улучшенное выражение is.The preceding small example demonstrates the enhancements to the is expression. Можно проверять типы значений, а также ссылочные типы; успешный результат можно назначить переменной соответствующего типа.You can test against value types as well as reference types, and you can assign the successful result to a new variable of the correct type.

Выражение сопоставления со switch имеет знакомый синтаксис, основанный на операторе switch, который уже является частью языка C#.The switch match expression has a familiar syntax, based on the switch statement already part of the C# language. Обновленный оператор switch имеет несколько новых конструкций:The updated switch statement has several new constructs:

  • Определяющий тип выражения switch больше не ограничен интегральными типами, типами Enum, string или типами, принимающими значения NULL, соответствующими одному из таких типов.The governing type of a switch expression is no longer restricted to integral types, Enum types, string, or a nullable type corresponding to one of those types. Может использоваться любой тип.Any type may be used.
  • Можно проверить тип выражения switch в каждой метке case.You can test the type of the switch expression in each case label. Как и в выражении is, можно назначить новую переменную этого типа.As with the is expression, you may assign a new variable to that type.
  • Можно добавить предложение when для дальнейшей проверки условий по этой переменной.You may add a when clause to further test conditions on that variable.
  • Порядок меток case становится важным.The order of case labels is now important. Будет выполнена первая совпавшая ветвь; другие пропускаются.The first branch to match is executed; others are skipped.

Это демонстрируется в следующем коде:The following code demonstrates these new features:

public static int SumPositiveNumbers(IEnumerable<object> sequence)
{
    int sum = 0;
    foreach (var i in sequence)
    {
        switch (i)
        {
            case 0:
                break;
            case IEnumerable<int> childSequence:
            {
                foreach(var item in childSequence)
                    sum += (item > 0) ? item : 0;
                break;
            }
            case int n when n > 0:
                sum += n;
                break;
            case null:
                throw new NullReferenceException("Null found in sequence");
            default:
                throw new InvalidOperationException("Unrecognized type");
        }
    }
    return sum;
}
  • case 0: — шаблон константы.case 0: is a constant pattern.
  • case IEnumerable<int> childSequence: — шаблон объявления.case IEnumerable<int> childSequence: is a declaration pattern.
  • case int n when n > 0: — шаблон объявления с дополнительным условием when.case int n when n > 0: is a declaration pattern with an additional when condition.
  • case null: — шаблон константы null.case null: is the null constant pattern.
  • default: — знакомый вариант по умолчанию.default: is the familiar default case.

Начиная с версии C# 7.1, выражение шаблона для шаблона типа is и switch может быть типом параметра универсального типа.Beginning with C# 7.1, the pattern expression for is and the switch type pattern may have the type of a generic type parameter. Эта возможность особенно полезна при проверке типов, которые могут представлять типы struct или class, когда вы хотите избежать упаковки-преобразования.This can be most useful when checking types that may be either struct or class types, and you want to avoid boxing.

Дополнительные сведения о сопоставлении шаблонов см. в разделе Сопоставление шаблонов в C#.You can learn more about pattern matching in Pattern Matching in C#.

Async mainAsync main

Метод async main позволяет использовать await в методе Main.An async main method enables you to use await in your Main method. Раньше пришлось бы написать:Previously you would need to write:

static int Main()
{
    return DoAsyncWork().GetAwaiter().GetResult();
}

Теперь можно написать:You can now write:

static async Task<int> Main()
{
    // This could also be replaced with the body
    // DoAsyncWork, including its await expressions:
    return await DoAsyncWork();
}

Если программа не возвращает код выхода, объявите метод Main, возвращающий Task:If your program doesn't return an exit code, you can declare a Main method that returns a Task:

static async Task Main()
{
    await SomeAsyncMethod();
}

См. подробнее в описании async main в руководстве по программированию.You can read more about the details in the async main article in the programming guide.

Локальные функцииLocal functions

Модели многих классов включают методы, вызываемые только из одного места.Many designs for classes include methods that are called from only one location. Эти дополнительные закрытые методы делают каждый метод небольшим и направленным.These additional private methods keep each method small and focused. Локальные функции позволяют объявлять методы в контексте другого метода.Local functions enable you to declare methods inside the context of another method. Локальные функции позволяют читателям класса легче увидеть, что локальный метод вызывается только из контекста, в котором он объявлен.Local functions make it easier for readers of the class to see that the local method is only called from the context in which it is declared.

Существуют два общих варианта использования локальных функций: открытые методы итератора и открытые асинхронные методы.There are two common use cases for local functions: public iterator methods and public async methods. Оба эти типа методов создают код, который сообщает об ошибках позднее, чем могли ожидать программисты.Both types of methods generate code that reports errors later than programmers might expect. В случае методов итератора исключения наблюдаются только при вызове кода, перечисляющего возвращенную последовательность.In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. В случае асинхронных методов исключения наблюдаются только при ожидании возвращаемого объекта Task.In async methods, any exceptions are only observed when the returned Task is awaited. В следующем примере показано отделение проверки параметров от реализации итератора с использованием локальной функции:The following example demonstrates separating parameter validation from the iterator implementation using a local function:

public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
            yield return c;
    }
}

Та же технология может применяться с методами async для того, чтобы исключения, возникающие при проверке параметров, выдавались до начала асинхронной работы:The same technique can be employed with async methods to ensure that exceptions arising from argument validation are thrown before the asynchronous work begins:

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}

Теперь поддерживается такой синтаксис:This syntax is now supported:

[field: SomeThingAboutFieldAttribute]
public int SomeProperty { get; set; }

Атрибут SomeThingAboutFieldAttribute применяется к резервному полю, созданному компилятором для SomeProperty.The attribute SomeThingAboutFieldAttribute is applied to the compiler generated backing field for SomeProperty. Дополнительные сведения см. в статье об атрибутах в руководстве по программированию на C#.For more information, see attributes in the C# programming guide.

Примечание

Некоторые конструкции, поддерживаемые локальными функциями, могут также выполняться с помощью лямбда-выражений.Some of the designs that are supported by local functions can also be accomplished using lambda expressions. Дополнительные сведения см. в статье Сравнение локальных функций и лямбда-выражений.For more information, see Local functions vs. lambda expressions.

Другие элементы, воплощающие выражениеMore expression-bodied members

В C# версии 6 появились элементы, воплощающие выражение, для функций-членов и свойств, доступных только для чтения.C# 6 introduced expression-bodied members for member functions and read-only properties. В C# 7.0 расширен список допустимых членов, которые могут быть реализованы как выражения.C# 7.0 expands the allowed members that can be implemented as expressions. В C# 7.0 можно реализовать конструкторы, методы завершения, а также методы доступа get и set для свойств и индексаторов.In C# 7.0, you can implement constructors, finalizers, and get and set accessors on properties and indexers. В следующем коде показаны примеры каждого из них:The following code shows examples of each:

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;

// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");

private string label;

// Expression-bodied get / set accessors.
public string Label
{
    get => label;
    set => this.label = value ?? "Default label";
}

Примечание

В этом примере метод завершения не требуется, он приводится для демонстрации синтаксиса.This example does not need a finalizer, but it is shown to demonstrate the syntax. Метод завершения следует реализовывать в классе только в том случае, если это необходимо для высвобождения неуправляемых ресурсов.You should not implement a finalizer in your class unless it is necessary to release unmanaged resources. Кроме того, вместо управления неуправляемыми ресурсами напрямую можно воспользоваться классом SafeHandle.You should also consider using the SafeHandle class instead of managing unmanaged resources directly.

Новые расположения для элементов, составляющих выражение, представляют важный этап развития языка C#. Эти функции были реализованы членами сообщества, работающими над проектом Roslyn с открытым исходным кодом.These new locations for expression-bodied members represent an important milestone for the C# language: These features were implemented by community members working on the open-source Roslyn project.

Изменение метода на элемент, воплощающий выражение, является совместимым на уровне двоичного кода.Changing a method to an expression bodied member is a binary compatible change.

Выражения throwThrow expressions

В C# throw всегда был оператором.In C#, throw has always been a statement. Поскольку throw — оператор, а не выражение, конструкции C# находились там, где использовать их было невозможно.Because throw is a statement, not an expression, there were C# constructs where you couldn't use it. Они включали условные выражения, выражения объединения со значением NULL и некоторые лямбда-выражения.These included conditional expressions, null coalescing expressions, and some lambda expressions. Добавление элементов, воплощающих выражение, расширяет список мест, в которых могут пригодиться выражения throw.The addition of expression-bodied members adds more locations where throw expressions would be useful. Для записи этих конструкций в C# 7.0 представлены выражения throw.So that you can write any of these constructs, C# 7.0 introduces throw expressions.

Это добавление упрощает написание кода на основе выражений.This addition makes it easier to write more expression-based code. Дополнительные инструкции для проверки на наличие ошибок не требуются.You don't need additional statements for error checking.

Литеральные выражения по умолчаниюDefault literal expressions

Литеральные выражения по умолчанию — это усовершенствование выражения значения по умолчанию.Default literal expressions are an enhancement to default value expressions. Эти выражения инициализируют переменную до значения по умолчанию.These expressions initialize a variable to the default value. Раньше пришлось бы написать:Where you previously would write:

Func<string, bool> whereClause = default(Func<string, bool>);

Теперь можно опустить тип с правой стороны инициализации:You can now omit the type on the right-hand side of the initialization:

Func<string, bool> whereClause = default;

Дополнительные сведения см. в разделе о литерале default в статье об операторе default.For more information, see the default literal section of the default operator article.

Усовершенствования в синтаксисе числовых литераловNumeric literal syntax improvements

Неправильное толкование числовых констант затрудняет понимание кода при первом прочтении.Misreading numeric constants can make it harder to understand code when reading it for the first time. Битовые маски или другие символьные значения могут вызывать затруднения.Bit masks or other symbolic values are prone to misunderstanding. C# 7.0 содержит две новые возможности для записи чисел в удобочитаемом виде: двоичные литералы и разделители цифр.C# 7.0 includes two new features to write numbers in the most readable fashion for the intended use: binary literals, and digit separators.

Если вы создаете битовые маски или двоичное представление числа дает наиболее удобочитаемый код, используйте запись в двоичном формате:For those times when you're creating bit masks, or whenever a binary representation of a number makes the most readable code, write that number in binary:

public const int Sixteen =   0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;

0b в начале константы означает, что число записано в двоичном формате.The 0b at the beginning of the constant indicates that the number is written as a binary number. Двоичные числа могут быть длинными, поэтому для удобства работы с битовыми шаблонами можно разделять разряды с помощью символа _, как показано в двоичной константе в предыдущем примере.Binary numbers can get long, so it's often easier to see the bit patterns by introducing the _ as a digit separator, as shown in the binary constant in the preceding example. Разделитель разрядов может находиться в любом месте константы.The digit separator can appear anywhere in the constant. В десятичных числах он обычно используется для разделения тысяч.For base 10 numbers, it is common to use it as a thousands separator. Шестнадцатеричные и двоичные числовые литералы могут начинаться со знака _:Hex and binary numeric literals may begin with an _:

public const long BillionsAndBillions = 100_000_000_000;

Разделитель разрядов можно также использовать с типами decimal, float и double:The digit separator can be used with decimal, float, and double types as well:

public const double AvogadroConstant = 6.022_140_857_747_474e23;
public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

Суммируя вышеизложенное, числовые константы можно объявлять в гораздо более удобочитаемом виде.Taken together, you can declare numeric constants with much more readability.

Переменные outout variables

Существующий синтаксис, поддерживающий параметры out, был улучшен в C# 7.The existing syntax that supports out parameters has been improved in C# 7. Переменные out можно объявлять в списке аргументов в вызове метода, не записывая отдельный оператор объявления:You can now declare out variables in the argument list of a method call, rather than writing a separate declaration statement:

if (int.TryParse(input, out int result))
    Console.WriteLine(result);
else
    Console.WriteLine("Could not parse input");

Для ясности можно указать тип переменной out, как показано в предыдущем примере.You may want to specify the type of the out variable for clarity, as shown in the preceding example. В то же время язык поддерживает использование неявно типизированной локальной переменной:However, the language does support using an implicitly typed local variable:

if (int.TryParse(input, out var answer))
    Console.WriteLine(answer);
else
    Console.WriteLine("Could not parse input");
  • Код проще читать.The code is easier to read.
    • Переменная out объявляется при использовании, а не в предыдущей строке кода.You declare the out variable where you use it, not on a preceding line of code.
  • Назначать начальное значение не нужно.No need to assign an initial value.
    • Объявляя переменную out, когда она используется при вызове метода, ее нельзя случайно использовать прежде, чем она будет назначена.By declaring the out variable where it's used in a method call, you can't accidentally use it before it is assigned.

Синтаксис, который с версии C# 7.0 позволяет объявлять переменные out, теперь также поддерживает инициализаторы полей, инициализаторы свойств, инициализаторы конструктора и предложения запроса.The syntax added in C# 7.0 to allow out variable declarations has been extended to include field initializers, property initializers, constructor initializers, and query clauses. Он позволяет создать такой код, как в следующем примере:It enables code such as the following example:

public class B
{
   public B(int i, out int j)
   {
      j = i;
   }
}

public class D : B
{
   public D(int i) : base(i, out var j)
   {
      Console.WriteLine($"The value of 'j' is {j}");
   }
}

Неконечные именованные аргументыNon-trailing named arguments

В вызовах методов после находящихся в правильной позиции именованных аргументов теперь можно использовать позиционные аргументы.Method calls may now use named arguments that precede positional arguments when those named arguments are in the correct positions. Дополнительные сведения см. в разделе Именованные и необязательные аргументы.For more information, see Named and optional arguments.

private protected — модификатор доступаprivate protected access modifier

Новый составной модификатор доступа private protected указывает, что доступ к члену может осуществляться содержащим классом или производными классами, которые объявлены в рамках одной сборки.A new compound access modifier: private protected indicates that a member may be accessed by containing class or derived classes that are declared in the same assembly. В отличие от модификатора protected internal, который разрешает доступ производным классам или классам из той же сборки, private protected ограничивает доступ только для производных классов, объявленных в рамках одной сборки.While protected internal allows access by derived classes or classes that are in the same assembly, private protected limits access to derived types declared in the same assembly.

Дополнительные сведения см. в разделе Модификаторы доступа в справочнике по языку.For more information, see access modifiers in the language reference.

Улучшенный отбор потенциальных перегрузокImproved overload candidates

В каждом выпуске обновляются правила разрешения перегрузок для устранения ситуаций, где неоднозначный вызов методов можно решить "очевидным" способом.In every release, the overload resolution rules get updated to address situations where ambiguous method invocations have an "obvious" choice. В этот выпуск добавлены три новых правила, которые помогают компилятору выбрать очевидный вариант.This release adds three new rules to help the compiler pick the obvious choice:

  1. Если группа методов содержит элементы экземпляра и статические элементы, компилятор отклоняет все элементы экземпляра при вызове метода без экземпляра-получателя и вне контекста экземпляра.When a method group contains both instance and static members, the compiler discards the instance members if the method was invoked without an instance receiver or context. Компилятор отклоняет статические элементы, если метод был вызван с экземпляром-получателем.The compiler discards the static members if the method was invoked with an instance receiver. Если получатель не указан, компилятор включает в статический контекст только статические элементы, а в противном случае — статические элементы и элементы экземпляра.When there is no receiver, the compiler includes only static members in a static context, otherwise both static and instance members. Если получатель невозможно однозначно определить как экземпляр или тип, компилятор включает и те, и другие элементы.When the receiver is ambiguously an instance or type, the compiler includes both. В статический контекст, в котором невозможно использовать неявный экземпляр-получатель this, включается текст тех элементов, для которых не определено this, например статические элементы, а также все места, где не может использоваться this, такие как инициализаторы полей и конструкторы-инициализаторы.A static context, where an implicit this instance receiver cannot be used, includes the body of members where no this is defined, such as static members, as well as places where this cannot be used, such as field initializers and constructor-initializers.
  2. Если группа методов содержит некоторые универсальные методы, у которых аргументы типа не удовлетворяют ограничениям, такие элементы удаляются из набора кандидатов.When a method group contains some generic methods whose type arguments do not satisfy their constraints, these members are removed from the candidate set.
  3. При преобразовании группы методов из набора удаляются методы-кандидаты, у которых возвращаемый тип не соответствует возвращаемому типу делегата.For a method group conversion, candidate methods whose return type doesn't match up with the delegate's return type are removed from the set.

Это изменение проявится только тем, что вы реже будете встречать ошибки компилятора о неоднозначной перегрузке методов в тех ситуациях, когда вы точно уверены в выборе лучшего метода.You'll only notice this change because you'll find fewer compiler errors for ambiguous method overloads when you are sure which method is better.

Повышение эффективности безопасного кодаEnabling more efficient safe code

Теперь вы сможете создавать безопасный код C#, который выполняется не хуже небезопасного кода.You should be able to write C# code safely that performs as well as unsafe code. Безопасный код позволяет избежать ошибок некоторых типов, таких как переполнение буфера, свободные указатели и другие ошибки доступа к памяти.Safe code avoids classes of errors, such as buffer overruns, stray pointers, and other memory access errors. Новые функции расширяют возможности гарантированно безопасного кода.These new features expand the capabilities of verifiable safe code. Старайтесь как можно большую часть кода создавать из безопасных конструкций.Strive to write more of your code using safe constructs. Благодаря новым возможностям это станет проще.These features make that easier.

В ту группу, которая отвечает за повышение производительности безопасного кода, входят следующие новые возможности:The following new features support the theme of better performance for safe code:

  • доступ к полям фиксированной ширины без закрепления;You can access fixed fields without pinning.
  • возможность переназначать локальные переменные ref;You can reassign ref local variables.
  • возможность использовать инициализаторы для массивов stackalloc;You can use initializers on stackalloc arrays.
  • возможность использовать инструкции fixed с любым типом, который поддерживает шаблон;You can use fixed statements with any type that supports a pattern.
  • возможность использовать дополнительные универсальные ограничения.You can use additional generic constraints.
  • модификатор in для параметров, указывающий, что аргумент передается по ссылке, но не изменяется вызываемым методом;The in modifier on parameters, to specify that an argument is passed by reference but not modified by the called method. Добавление модификатора in к аргументу является изменением, совместимым на уровне исходного кода.Adding the in modifier to an argument is a source compatible change.
  • модификатор ref readonly для возвращаемого значения метода, указывающий, что метод возвращает значение по ссылке, но не допускает операции записи в соответствующий объект;The ref readonly modifier on method returns, to indicate that a method returns its value by reference but doesn't allow writes to that object. Добавление модификатора ref readonly к аргументу является изменением, совместимым на уровне исходного кода, если оператору return присваивается значение.Adding the ref readonly modifier is a source compatible change, if the return is assigned to a value. Добавление модификатора readonly к существующему оператору return ref является несовместимым изменением.Adding the readonly modifier to an existing ref return statement is an incompatible change. Требуется указать вызывающие объекты, чтобы добавить модификатор readonly в объявление локальных переменных ref.It requires callers to update the declaration of ref local variables to include the readonly modifier.
  • объявление readonly struct, указывающее, что структура является неизменяемой и должна передаваться в методы члена как параметр in;The readonly struct declaration, to indicate that a struct is immutable and should be passed as an in parameter to its member methods. Добавление модификатора readonly к существующему объявлению структуры является двоично совместимым изменением.Adding the readonly modifier to an existing struct declaration is a binary compatible change.
  • объявление ref struct, указывающее, что тип структуры обращается напрямую к управляемой памяти и всегда должен обрабатываться с выделением стека.The ref struct declaration, to indicate that a struct type accesses managed memory directly and must always be stack allocated. Добавление модификатора ref к существующему объявлению struct является двоично совместимым изменением.Adding the ref modifier to an existing struct declaration is an incompatible change. Объект ref struct не может быть членом класса или использоваться в других местах, где он может выделяться в куче.A ref struct cannot be a member of a class or used in other locations where it may be allocated on the heap.

Дополнительные сведения обо всех эти изменениях см. в статье о том, как писать безопасный и эффективный код.You can read more about all these changes in Write safe efficient code.

Локальные переменные и возвращаемые значения RefRef locals and returns

Эта функция активирует алгоритмы, которые используют и возвращают ссылки на переменные, определенные в другом месте.This feature enables algorithms that use and return references to variables defined elsewhere. В качестве примера можно привести работу с большими матрицами и поиск одного местоположения с определенными характеристиками.One example is working with large matrices, and finding a single location with certain characteristics. Следующий метод возвращает ссылку на это хранилище в матрице:The following method returns a reference to that storage in the matrix:

public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return ref matrix[i, j];
    throw new InvalidOperationException("Not found");
}

Можно объявить возвращаемое значение как ref и изменять это значение в матрице, как показано в следующем коде:You can declare the return value as a ref and modify that value in the matrix, as shown in the following code:

ref var item = ref MatrixSearch.Find(matrix, (val) => val == 42);
Console.WriteLine(item);
item = 24;
Console.WriteLine(matrix[4, 2]);

Язык C# включает правила, которые защищают вас от неправильного использования локальных переменных и возвращаемых значений ref:The C# language has several rules that protect you from misusing the ref locals and returns:

  • Необходимо добавить ключевое слово ref в сигнатуру метода и все инструкции return в методе.You must add the ref keyword to the method signature and to all return statements in a method.
    • Это позволяет уточнить, что метод возвращает значение по ссылке, во всех местах.That makes it clear the method returns by reference throughout the method.
  • Объект ref return может быть назначен переменной-значению или переменной ref.A ref return may be assigned to a value variable, or a ref variable.
    • Вызывающий объект определяет, копируется ли возвращаемое значение.The caller controls whether the return value is copied or not. Пропуск модификатора ref при присваивании возвращаемого значения указывает, что вызывающий объект хочет получить копию значения, а не ссылку на хранилище.Omitting the ref modifier when assigning the return value indicates that the caller wants a copy of the value, not a reference to the storage.
  • Присвоить локальной переменной ref стандартное возвращаемое значение метода невозможно.You can't assign a standard method return value to a ref local variable.
    • Это запрещает использовать операторы вида ref int i = sequence.Count();That disallows statements like ref int i = sequence.Count();
  • Переменную ref невозможно возвращать переменной, которая продолжает существовать даже после того, как метод будет выполнен.You can't return a ref to a variable whose lifetime doesn't extend beyond the execution of the method.
    • Это означает невозможность возвращения ссылки на локальную переменную или переменную с аналогичной областью.That means you can't return a reference to a local variable or a variable with a similar scope.
  • Возвращаемые значения и локальные переменные ref не могут использоваться с асинхронными методами.ref locals and returns can't be used with async methods.
    • На момент, когда асинхронный метод возвращает значение, компилятору неизвестно, присвоено ли переменной, на которую указывает ссылка, окончательное значение.The compiler can't know if the referenced variable has been set to its final value when the async method returns.

Добавление локальных переменных и возвращаемых значений ref дает возможность использовать более эффективные алгоритмы, поскольку избавляет от необходимости многократно копировать значения или выполнять операции разыменования.The addition of ref locals and ref returns enables algorithms that are more efficient by avoiding copying values, or performing dereferencing operations multiple times.

Добавление ref для возврата значения является изменением, совместимым на уровне исходного кода.Adding ref to the return value is a source compatible change. Существующий код компилируется, но возвращаемое значение ссылочного типа копируется при назначении.Existing code compiles, but the ref return value is copied when assigned. Вызывающие объекты должны изменить переменную хранилища для возвращаемого значения на локальную переменную ref, чтобы это значение хранилось в качестве ссылки.Callers must update the storage for the return value to a ref local variable to store the return as a reference.

Теперь локальные переменные ref можно переназначить другим экземплярам после инициализации.Now, ref locals may be reassigned to refer to different instances after being initialized. Следующая команда теперь успешно компилируется:The following code now compiles:

ref VeryLargeStruct refLocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.

Дополнительные сведения см. в статье о возвращаемых значениях ref и локальных переменных ref, а также в статье foreach.For more information, see the article on ref returns and ref locals, and the article on foreach.

Дополнительные сведения см. в статье ref (Справочник по C#).For more information, see the ref keyword article.

Условные выражения refConditional ref expressions

Результат условного выражения может быть ссылкой, а не значением.Finally, the conditional expression may produce a ref result instead of a value result. Например, можно написать следующий код, чтобы получить ссылку на первый элемент в одном из двух массивов:For example, you would write the following to retrieve a reference to the first element in one of two arrays:

ref var r = ref (arr != null ? ref arr[0] : ref otherArr[0]);

Переменная r — это ссылка на первое значение в arr или otherArr.The variable r is a reference to the first value in either arr or otherArr.

Дополнительные сведения см. в описании условного оператора (?:) в справочнике по языку.For more information, see conditional operator (?:) in the language reference.

Модификатор параметра inin parameter modifier

При передаче аргументов по ссылке можно использовать ключевое слово in как дополнение к существующим ключевым словам ref и out.The in keyword complements the existing ref and out keywords to pass arguments by reference. Ключевое слово in указывает, что аргумент передается по ссылке, но вызванный метод не изменяет это значение.The in keyword specifies passing the argument by reference, but the called method doesn't modify the value.

Перегрузки, передаваемые по значению или ссылке "только для чтения", можно объявлять, как показано в следующем коде:You may declare overloads that pass by value or by readonly reference, as shown in the following code:

static void M(S arg);
static void M(in S arg);

Перегрузка по значению (первая в предыдущем примере) считается лучше, чем перегрузка по атрибуту "только для чтения".The by value (first in the preceding example) overload is better than the by readonly reference version. Чтобы вызвать версию со ссылочным аргументом "только для чтения", необходимо при вызове метода указать модификатор in.To call the version with the readonly reference argument, you must include the in modifier when calling the method.

Дополнительные сведения см. в статье, посвященной модификатору параметра in.For more information, see the article on the in parameter modifier.

Больше типов поддерживают инструкцию fixedMore types support the fixed statement

Инструкция fixed ранее поддерживала лишь ограниченный набор типов.The fixed statement supported a limited set of types. Начиная с C# 7.3 любой тип, содержащий метод GetPinnableReference(), который возвращает ref T или ref readonly T, может иметь инструкцию fixed.Starting with C# 7.3, any type that contains a GetPinnableReference() method that returns a ref T or ref readonly T may be fixed. Добавление этой возможности означает, что fixed можно применять для System.Span<T> и связанных типов.Adding this feature means that fixed can be used with System.Span<T> and related types.

Дополнительные сведения см. в статье об инструкции fixed в справочнике по языку.For more information, see the fixed statement article in the language reference.

Индексирование полей fixed не требует закрепленияIndexing fixed fields does not require pinning

Давайте рассмотрим такую структуру:Consider this struct:

unsafe struct S
{
    public fixed int myFixedField[10];
}

В более ранних версиях C# переменную необходимо закрепить, чтобы получить доступ к целым числам, входящим в myFixedField.In earlier versions of C#, you needed to pin a variable to access one of the integers that are part of myFixedField. Теперь приведенный ниже код компилируется без закрепления переменной p внутри отдельного оператора fixed:Now, the following code compiles without pinning the variable p inside a separate fixed statement:

class C
{
    static S s = new S();

    unsafe public void M()
    {
        int p = s.myFixedField[5];
    }
}

Переменная p обращается к одному элементу в myFixedField.The variable p accesses one element in myFixedField. Для этого не нужно объявлять отдельную переменную int*.You don't need to declare a separate int* variable. Контекст unsafe по-прежнему является обязательным.You still need an unsafe context. В более ранних версиях C# необходимо объявить второй фиксированный указатель:In earlier versions of C#, you need to declare a second fixed pointer:

class C
{
    static S s = new S();

    unsafe public void M()
    {
        fixed (int* ptr = s.myFixedField)
        {
            int p = ptr[5];
        }
    }
}

Дополнительные сведения см. в статье, посвященной инструкции fixed.For more information, see the article on the fixed statement.

Массивы stackalloc поддерживают инициализаторыstackalloc arrays support initializers

Раньше вы могли задавать значения для элементов массива при его инициализации:You've been able to specify the values for elements in an array when you initialize it:

var arr = new int[3] {1, 2, 3};
var arr2 = new int[] {1, 2, 3};

Теперь такой же синтаксис можно применять к массивам, в объявлении которых есть stackalloc:Now, that same syntax can be applied to arrays that are declared with stackalloc:

int* pArr = stackalloc int[3] {1, 2, 3};
int* pArr2 = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};

Дополнительные сведения см. в статье Оператор stackalloc.For more information, see the stackalloc operator article.

Расширенные универсальные ограниченияEnhanced generic constraints

Теперь вы можете указать тип System.Enum или System.Delegate в качестве ограничения базового класса для параметра типа.You can now specify the type System.Enum or System.Delegate as base class constraints for a type parameter.

Вы также можете использовать новое ограничение unmanaged, чтобы указать, что параметр типа должен быть неуправляемым типом, не допускающим значения NULL.You can also use the new unmanaged constraint, to specify that a type parameter must be a non-nullable unmanaged type.

Дополнительные сведения см. в статьях об универсальных ограничениях where и ограничениях параметров типа.For more information, see the articles on where generic constraints and constraints on type parameters.

Добавление этих ограничений в существующие типы — это несовместимое изменение.Adding these constraints to existing types is an incompatible change. Закрытые универсальные типы не будут соответствовать подобным ограничениям.Closed generic types may no longer meet these new constraints.

Обобщенные асинхронные типы возвращаемых значенийGeneralized async return types

В некоторых случаях возврат объекта Task из асинхронных методов может вызывать сложности.Returning a Task object from async methods can introduce performance bottlenecks in certain paths. Task — это тип ссылки, поэтому его применение означает распределение объекта.Task is a reference type, so using it means allocating an object. В случаях, когда метод, объявленный с модификатором async, возвращает кэшированный результат или завершается синхронно, лишние распределения могут вызывать серьезные потери времени при выполнении фрагментов кода, зависящих от производительности.In cases where a method declared with the async modifier returns a cached result, or completes synchronously, the extra allocations can become a significant time cost in performance critical sections of code. Эта проблема встает серьезно, если распределения происходят в коротких циклах.It can become costly if those allocations occur in tight loops.

Новая возможность языка означает, что этот асинхронный метод возвращает типы, не ограниченные Task, Task<T> и void.The new language feature means that async method return types aren't limited to Task, Task<T>, and void. Возвращаемый тип должен по-прежнему соответствовать асинхронному шаблону, а значит, метод GetAwaiter должен быть доступен.The returned type must still satisfy the async pattern, meaning a GetAwaiter method must be accessible. Конкретный пример. В .NET добавлен новый тип ValueTask, позволяющий применять эту новую возможность языка:As one concrete example, the ValueTask type has been added to .NET to make use of this new language feature:

public async ValueTask<int> Func()
{
    await Task.Delay(100);
    return 5;
}

Примечание

Чтобы использовать тип ValueTask<TResult>, необходимо добавить пакет NuGet System.Threading.Tasks.Extensions >.You need to add the NuGet package System.Threading.Tasks.Extensions > in order to use the ValueTask<TResult> type.

Это улучшение особенно полезно для авторов библиотек, которые хотят избежать выделения Task в критическом по производительности коде.This enhancement is most useful for library authors to avoid allocating a Task in performance critical code.

Новые параметры компилятораNew compiler options

Новые параметры компилятора поддерживают сценарии сборки и DevOps для программ на C#.New compiler options support new build and DevOps scenarios for C# programs.

Создание базовой сборкиReference assembly generation

Доступно два новых параметра компилятора, которые создают сборки только со ссылками: ProduceReferenceAssembly и ProduceOnlyReferenceAssembly.There are two new compiler options that generate reference-only assemblies: ProduceReferenceAssembly and ProduceOnlyReferenceAssembly. В соответствующих статьях подробно рассматриваются эти параметры и базовые сборки.The linked articles explain these options and reference assemblies in more detail.

Подписывание открытым ключом или с открытым исходным кодомPublic or Open Source signing

Параметр компилятора PublicSign указывает, что сборку нужно подписать открытым ключом.The PublicSign compiler option instructs the compiler to sign the assembly using a public key. Такая сборка будет помечена как подписанная, но подпись для нее берется из открытого ключа.The assembly is marked as signed, but the signature is taken from the public key. Этот параметр позволяет создавать подписанные сборки из проектов с открытым кодом с помощью открытого ключа.This option enables you to build signed assemblies from open-source projects using a public key.

Дополнительные сведения см. в статье о параметре компилятора PublicSign.For more information, see the PublicSign compiler option article.

pathmappathmap

Параметр PathMap указывает компилятору, что исходные пути в среде сборки следует заменить сопоставленными исходными путями.The PathMap compiler option instructs the compiler to replace source paths from the build environment with mapped source paths. Параметр PathMap управляет исходными путями, которые компилятор записывает в PDB-файлы или для CallerFilePathAttribute.The PathMap option controls the source path written by the compiler to PDB files or for the CallerFilePathAttribute.

Дополнительные сведения см. в статье о параметре компилятора PathMap.For more information, see the PathMap compiler option article.