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

В C# 7.0 язык C# получает ряд новых возможностей:C# 7.0 adds a number of new features to the C# language:

  • Переменные outout variables
    • Значения out можно объявлять встроенными как аргументы для метода, в котором они используются.You can declare out values inline as arguments to the method where they're used.
  • КортежиTuples
    • Вы можете создать простые, неименованные типы, содержащие несколько открытых полей.You can create lightweight, unnamed types that contain multiple public fields. Компиляторы и инструменты IDE понимают семантику этих типов.Compilers and IDE tools understand the semantics of these types.
  • Операции удаленияDiscards
    • Пустые переменные представляют собой временные переменные, доступные только для записи, которые используются при присвоении в тех случаях, когда присваиваемое значение не важно.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.
  • Локальные переменные и возвращаемые значения refref locals and returns
    • Локальные переменные метода и возвращаемые значения могут быть ссылками на другое хранилище.Method local variables and return values can be references to other storage.
  • Локальные функции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.
  • Обобщенные асинхронные типы возвращаемых значенийGeneralized async return types
    • Методы, объявленные с модификатором async, могут возвращать другие типы помимо Task и Task<T>.Methods declared with the async modifier can return other types in addition to Task and Task<T>.
  • Усовершенствования в синтаксисе числовых литераловNumeric literal syntax improvements
    • Новые маркеры делают числовые константы более удобочитаемыми.New tokens improve readability for numeric constants.

В оставшейся части этой статьи представлены общие сведения об этих функциях.The remainder of this article provides an overview of each feature. Каждая функция сопровождается обоснованием.For each feature, you'll learn the reasoning behind it. Вы изучите синтаксисYou'll learn 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.

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

Существующий синтаксис, поддерживающий параметры out, в этой версии был улучшен.The existing syntax that supports out parameters has been improved in this version. Переменные 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 above. В то же время язык поддерживает использование неявно типизированной локальной переменной: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 another line above.
  • Назначать начальное значение не нужно.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.

КортежиTuples

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#, предшествовавших версии 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 runtime.

В назначении кортежа можно также указать имена полей в правой части назначения: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;

Дополнительные сведения о кортежах см. в статье о кортежах.You can learn more in depth about tuples in the tuples article.

Пустые переменныеDiscards

При деконструкции кортежа или вызове метода с параметрами 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 feature that allows you to implement method dispatch on properties other than the type of an object. Возможно, вы уже знакомы с отправкой метода на основе типа объекта.You're probably already familiar with method dispatch based on the type of an object. В объектно-ориентированном программировании виртуальные и переопределяющие методы предоставляют синтаксис языка, позволяющий отправлять метод, исходя из типа объекта.In object-oriented programming, virtual and override methods provide language syntax to implement method dispatching based on an object's type. Базовый и производный классы предусматривают различные реализации.Base and Derived classes provide different implementations. Выражения сопоставления шаблонов расширяют эту концепцию, позволяя легко внедрять аналогичные шаблоны отправки для типов и элементов данных, не связанных иерархией наследования.Pattern matching expressions extend this concept so that you can easily implement similar dispatch patterns for types and data elements that aren't related through an inheritance hierarchy.

Сопоставление шаблонов поддерживает выражения 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 the familiar constant pattern.
  • case IEnumerable<int> childSequence: — шаблон типа.case IEnumerable<int> childSequence: is a type pattern.
  • case int n when n > 0: — шаблон типа с дополнительным условием when.case int n when n > 0: is a type pattern with an additional when condition.
  • case null: — шаблон NULL.case null: is the null pattern.
  • default: — знакомый вариант по умолчанию.default: is the familiar default case.

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

Локальные переменные и возвращаемые значения 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 (Справочник по C#).For more information, see the ref keyword article.

Локальные функции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.";
    }
}

Примечание

Некоторые из макетов, поддерживаемых локальными функциями, также могут выполняться с помощью лямбда-выражений.Some of the designs that are supported by local functions could 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.

Обобщенные асинхронные типы возвращаемых значений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 Framework добавлен новый тип ValueTask, позволяющий использовать эту новую возможность языка:As one concrete example, the ValueTask type has been added to the .NET framework 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.

Усовершенствования в синтаксисе числовых литералов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 above in the binary constant. Разделитель разрядов может находиться в любом месте константы.The digit separator can appear anywhere in the constant. В десятичных числах он обычно используется для разделения тысяч:For base 10 numbers, it is common to use it as a thousands separator:

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.