Поделиться через


Сопоставление шаблонов — is выражения и switch операторы and, orа также not в шаблонах

Вы используете is выражение, оператор switch и выражение переключателя для сопоставления входного выражения с любым количеством характеристик. C# поддерживает несколько шаблонов, включая объявление, тип, константу, реляционную, свойство, список, вар и отмену. Шаблоны можно объединить с помощью логических ключевых слов логики and, orи not.

Сопоставление шаблонов поддерживают следующие выражения и операторы C#:

В этих конструкциях можно сравнить входное выражение с любым из следующих шаблонов:

  • Шаблон объявления: для проверки типа выражения в среде выполнения и, в случае равенства, присвоения объявленной переменной результата этого выражения.
  • Шаблон типа: для проверки типа выражения в среде выполнения.
  • Шаблон константы: для проверки того, что результат выражения равен заданной константе.
  • Реляционные шаблоны: для сравнения результата выражения с заданной константой.
  • Логические шаблоны: чтобы проверить, соответствует ли выражение логическому сочетанию шаблонов.
  • Шаблон свойств: чтобы проверить, соответствуют ли свойства или поля выражения вложенным шаблонам.
  • Позиционный шаблон: для деконструкции результата выражения и проверки того, соответствуют ли результирующие значения вложенным шаблонам.
  • Шаблон var: для сравнения любых выражений и присваивания результата сравнения объявленной переменной.
  • Шаблон пустой переменной: для сравнения любых выражений.
  • Шаблоны списка: чтобы проверить, соответствует ли последовательность элементов соответствующим вложенным шаблонам. Представлено в C# 11.

Логические, свойства, позиционные и списковые шаблоны являются рекурсивными шаблонами. То есть, они могут содержать вложенные шаблоны.

Пример использования этих шаблонов для создания управляемого данными алгоритма можно посмотреть в разделе Учебник: использование сопоставления шаблонов для создания управляемых типами и управляемых данными алгоритмов.

Шаблоны объявления и шаблоны типов

Шаблоны объявления и шаблоны типов используются для проверки того, совместим ли с указанным типом тип определенного выражения в среде выполнения. С помощью шаблона объявления можно также объявить новую локальную переменную. Если шаблон объявления соответствует выражению, этой переменной присваивается результат преобразованного выражения, как показано в следующем примере:

object greeting = "Hello, World!";
if (greeting is string message)
{
    Console.WriteLine(message.ToLower());  // output: hello, world!
}

Шаблон объявления с типом T соответствует выражению, если результат выражения не имеет значения NULL, и любое из следующих условий имеет значение true:

  • Тип результата выражения в среде выполнения — T.

  • Тип результата выражения в среде выполнения является производным от типа T, реализует интерфейс T или существует другое неявное преобразование ссылок из него в T. В следующем примере показаны два случая, когда данное условие истинно:

    var numbers = new int[] { 10, 20, 30 };
    Console.WriteLine(GetSourceLabel(numbers));  // output: 1
    
    var letters = new List<char> { 'a', 'b', 'c', 'd' };
    Console.WriteLine(GetSourceLabel(letters));  // output: 2
    
    static int GetSourceLabel<T>(IEnumerable<T> source) => source switch
    {
        Array array => 1,
        ICollection<T> collection => 2,
        _ => 3,
    };
    

    В предыдущем примере при первом вызове метода GetSourceLabel первый шаблон соответствует значению аргумента, так как тип этого аргумента (int[]) в среде выполнения является производным от типа Array. При втором вызове метода GetSourceLabel тип аргумента в среде выполнения (List<T>) не является производным от типа Array, но реализует интерфейс ICollection<T>.

  • Тип результата выражения в среде выполнения является типом, допускающим значение NULL, и имеющим базовый тип T.

  • Существует упаковка-преобразование или распаковка-преобразование из типа результата выражения в среде выполнения в тип T.

В следующем примере показаны два последних условия:

int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
    Console.WriteLine(a + b);  // output: 30
}

Если требуется проверить только тип выражения, можно вместо имени переменной использовать пустую переменную _, как показано в следующем примере:

public abstract class Vehicle {}
public class Car : Vehicle {}
public class Truck : Vehicle {}

public static class TollCalculator
{
    public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
    {
        Car _ => 2.00m,
        Truck _ => 7.50m,
        null => throw new ArgumentNullException(nameof(vehicle)),
        _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
    };
}

Для этого можно использовать шаблон типа, как показано в следующем примере:

public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{
    Car => 2.00m,
    Truck => 7.50m,
    null => throw new ArgumentNullException(nameof(vehicle)),
    _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
};

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

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

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

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

Шаблон константы

Для проверки результата выражения используется шаблон константы, как показано в следующем примере:

public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
    1 => 12.0m,
    2 => 20.0m,
    3 => 27.0m,
    4 => 32.0m,
    0 => 0.0m,
    _ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),
};

В шаблоне константы можно использовать любое константное выражение, например:

Выражение должно быть типом, который преобразуется в тип константы, за исключением одного исключения: выражение, тип которого совпадает Span<char> ReadOnlySpan<char> с константными строками в C# 11 и более поздних версиях.

Используйте шаблон константы для проверки на null, как показано в следующем примере:

if (input is null)
{
    return;
}

Компилятор гарантирует, что при вычислении выражения x is null не будет вызван перегруженный пользователем оператор равенства ==.

Для проверки наличия непустой константы можно использовать ненулевой null шаблон, как показано в следующем примере:

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

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

Реляционные шаблоны

Для сравнения результата выражения с константой используется реляционный шаблон , как показано в следующем примере:

Console.WriteLine(Classify(13));  // output: Too high
Console.WriteLine(Classify(double.NaN));  // output: Unknown
Console.WriteLine(Classify(2.4));  // output: Acceptable

static string Classify(double measurement) => measurement switch
{
    < -4.0 => "Too low",
    > 10.0 => "Too high",
    double.NaN => "Unknown",
    _ => "Acceptable",
};

В реляционном шаблоне можно использовать любые операторы отношений — <, >, <=, >=. Правая часть реляционного шаблона должна быть константным выражением. Константное выражение может быть целым числом, числом с плавающей точкой, символом или иметь тип enum.

Чтобы проверить, находится ли результат выражения в определенном диапазоне, сопоставьте его с шаблоном конъюнкции (and), как показано в следующем примере:

Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14)));  // output: spring
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19)));  // output: summer
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17)));  // output: winter

static string GetCalendarSeason(DateTime date) => date.Month switch
{
    >= 3 and < 6 => "spring",
    >= 6 and < 9 => "summer",
    >= 9 and < 12 => "autumn",
    12 or (>= 1 and < 3) => "winter",
    _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
};

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

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

Логические шаблоны

Для создания следующих логических шаблонов используются notandкомбинаторы шаблонов и or шаблонов:

  • Шаблон отрицания not, который соответствует выражению, если шаблон с отрицанием не соответствует выражению. В следующем примере показано, как можно инвертировать шаблон константы null, чтобы проверить, имеет ли выражение значение, отличное от NULL:

    if (input is not null)
    {
        // ...
    }
    
  • Шаблон конъюнкции and, который соответствует выражению, если оба шаблона соответствуют этому выражению. В следующем примере показано, как можно объединить реляционные шаблоны, чтобы проверить, находится ли значение в определенном диапазоне:

    Console.WriteLine(Classify(13));  // output: High
    Console.WriteLine(Classify(-100));  // output: Too low
    Console.WriteLine(Classify(5.7));  // output: Acceptable
    
    static string Classify(double measurement) => measurement switch
    {
        < -40.0 => "Too low",
        >= -40.0 and < 0 => "Low",
        >= 0 and < 10.0 => "Acceptable",
        >= 10.0 and < 20.0 => "High",
        >= 20.0 => "Too high",
        double.NaN => "Unknown",
    };
    
  • Отсоставленный or шаблон, соответствующий выражению, когда любой шаблон соответствует выражению, как показано в следующем примере:

    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));  // output: winter
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));  // output: autumn
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));  // output: spring
    
    static string GetCalendarSeason(DateTime date) => date.Month switch
    {
        3 or 4 or 5 => "spring",
        6 or 7 or 8 => "summer",
        9 or 10 or 11 => "autumn",
        12 or 1 or 2 => "winter",
        _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
    };
    

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

Приоритет и порядок проверки

Комбинаторы шаблонов упорядочены на основе порядка привязки выражений следующим образом:

  • not
  • and
  • or

Шаблон not сначала привязывается к операнду. Шаблон and привязывается после любой not привязки выражения шаблона. Шаблон or привязывается после всех not и and шаблонов привязаны к операндам. В следующем примере показано, как сопоставить все символы, которые не являются строчными буквами. a - z Она имеет ошибку, так как not шаблон привязывается к шаблону and :

// Incorrect pattern. `not` binds before `and`
static bool IsNotLowerCaseLetter(char c) => c is not >= 'a' and <= 'z';

Привязка по умолчанию означает, что предыдущий пример анализируется в следующем примере:

// The default binding without parentheses is shows in this method. `not` binds before `and`
static bool IsNotLowerCaseLetterDefaultBinding(char c) => c is ((not >= 'a') and <= 'z');

Чтобы исправить его, необходимо указать, что необходимо not привязать к выражению >= 'a' and <= 'z' :

// Correct pattern. Force `and` before `not`
static bool IsNotLowerCaseLetterParentheses(char c) => c is not (>= 'a' and <= 'z');

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

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

Примечание.

Порядок проверки шаблонов с одинаковым порядком привязки не определен. Во время выполнения можно сначала проверить вложенные справа шаблоны нескольких or шаблонов и нескольких and шаблонов.

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

Шаблон свойства

Шаблон свойства используется для сопоставления свойств или полей выражения с вложенными шаблонами, как показано в следующем примере:

static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };

Шаблон свойства соответствует выражению, если результат выражения не равен NULL, а каждый вложенный шаблон соответствует соответствующему свойству или полю результата выражения.

Вы также можете добавить проверку типа среды выполнения и объявление переменной в шаблон свойства, как показано в следующем примере:

Console.WriteLine(TakeFive("Hello, world!"));  // output: Hello
Console.WriteLine(TakeFive("Hi!"));  // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' }));  // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' }));  // output: abc

static string TakeFive(object input) => input switch
{
    string { Length: >= 5 } s => s.Substring(0, 5),
    string s => s,

    ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
    ICollection<char> symbols => new string(symbols.ToArray()),

    null => throw new ArgumentNullException(nameof(input)),
    _ => throw new ArgumentException("Not supported input type."),
};

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

public record Point(int X, int Y);
public record Segment(Point Start, Point End);

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start: { Y: 0 } } or { End: { Y: 0 } };

В предыдущем примере используется or комбинатор шаблонов и типы записей.

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

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start.Y: 0 } or { End.Y: 0 };

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

Совет

Для улучшения удобочитаемости кода можно использовать правило стиля упрощения шаблона свойств (IDE0170), предлагая места для использования шаблонов расширенных свойств.

Позиционный шаблон

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

public readonly struct Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) => (X, Y) = (x, y);

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

static string Classify(Point point) => point switch
{
    (0, 0) => "Origin",
    (1, 0) => "positive X basis end",
    (0, 1) => "positive Y basis end",
    _ => "Just a point",
};

В предыдущем примере тип выражения содержит метод Deconstruct, используемый для деконструкции результата выражения.

Внимание

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

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

static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate)
    => (groupSize, visitDate.DayOfWeek) switch
    {
        (<= 0, _) => throw new ArgumentException("Group size must be positive."),
        (_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
        (>= 5 and < 10, DayOfWeek.Monday) => 20.0m,
        (>= 10, DayOfWeek.Monday) => 30.0m,
        (>= 5 and < 10, _) => 12.0m,
        (>= 10, _) => 15.0m,
        _ => 0.0m,
    };

В предыдущем примере используются реляционные и логические шаблоны.

В позиционном шаблоне можно использовать имена элементов кортежа и параметры метода Deconstruct, как показано в следующем примере:

var numbers = new List<int> { 1, 2, 3 };
if (SumAndCount(numbers) is (Sum: var sum, Count: > 0))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");  // output: Sum of [1 2 3] is 6
}

static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers)
{
    int sum = 0;
    int count = 0;
    foreach (int number in numbers)
    {
        sum += number;
        count++;
    }
    return (sum, count);
}

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

  • Добавьте проверку типа среды выполнения и объявление переменной, как показано в следующем примере:

    public record Point2D(int X, int Y);
    public record Point3D(int X, int Y, int Z);
    
    static string PrintIfAllCoordinatesArePositive(object point) => point switch
    {
        Point2D (> 0, > 0) p => p.ToString(),
        Point3D (> 0, > 0, > 0) p => p.ToString(),
        _ => string.Empty,
    };
    

    В предыдущем примере используются позиционные записи, которые неявно обеспечивают выполнение метода Deconstruct.

  • Используйте шаблон свойства в позиционном шаблоне, как показано в следующем примере:

    public record WeightedPoint(int X, int Y)
    {
        public double Weight { get; set; }
    }
    
    static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
    
  • Можно объединить два предыдущих варианта, как показано в следующем примере:

    if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p)
    {
        // ..
    }
    

Позиционный шаблон является рекурсивным шаблоном. Это значит, что любой шаблон можно использовать как вложенный шаблон.

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

Шаблон var

Шаблон используется var для сопоставления любого выражения, в том числе nullи назначения результата новой локальной переменной, как показано в следующем примере:

static bool IsAcceptable(int id, int absLimit) =>
    SimulateDataFetch(id) is var results 
    && results.Min() >= -absLimit 
    && results.Max() <= absLimit;

static int[] SimulateDataFetch(int id)
{
    var rand = new Random();
    return Enumerable
               .Range(start: 0, count: 5)
               .Select(s => rand.Next(minValue: -10, maxValue: 11))
               .ToArray();
}

Шаблон var полезно использовать, если в логическом выражении вам требуется временная переменная для хранения результатов промежуточных вычислений. Вы также можете использовать var шаблон, если необходимо выполнить дополнительные проверки в when случае защиты switch выражения или инструкции, как показано в следующем примере:

public record Point(int X, int Y);

static Point Transform(Point point) => point switch
{
    var (x, y) when x < y => new Point(-x, y),
    var (x, y) when x > y => new Point(x, -y),
    var (x, y) => new Point(x, y),
};

static void TestTransform()
{
    Console.WriteLine(Transform(new Point(1, 2)));  // output: Point { X = -1, Y = 2 }
    Console.WriteLine(Transform(new Point(5, 2)));  // output: Point { X = 5, Y = -2 }
}

В предыдущем примере шаблон var (x, y) эквивалентен позиционному шаблону (var x, var y).

В шаблоне var тип объявленной переменной равен установленному во время компиляции типу выражения, сопоставляемого с данным шаблоном.

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

Шаблон пустой переменной

Шаблон отмены _ используется для сопоставления любого выражения, в том числеnull, как показано в следующем примере:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));  // output: 5.0
Console.WriteLine(GetDiscountInPercent(null));  // output: 0.0
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));  // output: 0.0

static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch
{
    DayOfWeek.Monday => 0.5m,
    DayOfWeek.Tuesday => 12.5m,
    DayOfWeek.Wednesday => 7.5m,
    DayOfWeek.Thursday => 12.5m,
    DayOfWeek.Friday => 5.0m,
    DayOfWeek.Saturday => 2.5m,
    DayOfWeek.Sunday => 2.0m,
    _ => 0.0m,
};

В предыдущем примере шаблон пустой переменной используется для обработки значения null и любых целочисленных значений, которые не соответствуют имеющимся членам перечисления DayOfWeek. Благодаря этому гарантируется, что выражение switch в приведенном примере сможет обработать все возможные входные значения. Если в выражении switch не используется шаблон пустой переменной и при этом ни один из шаблонов выражения не соответствует входным данным, среда выполнения генерирует исключение. Если выражение switch не обрабатывает все возможные входные значения, компилятор генерирует предупреждение.

Шаблон отмены не может быть шаблоном is в выражении или операторе switch . В этих случаях для сопоставления выражений используйте шаблон var с пустой переменной: var _. Шаблон отмены может быть шаблоном switch в выражении.

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

Шаблон в круглых скобках

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

if (input is not (float or double))
{
    return;
}

Шаблоны списков

Начиная с C# 11, можно сопоставить массив или список с последовательностью шаблонов, как показано в следующем примере:

int[] numbers = { 1, 2, 3 };

Console.WriteLine(numbers is [1, 2, 3]);  // True
Console.WriteLine(numbers is [1, 2, 4]);  // False
Console.WriteLine(numbers is [1, 2, 3, 4]);  // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]);  // True

Как показано в предыдущем примере, шаблон списка сопоставляется, когда каждый вложенный шаблон соответствует соответствующему элементу входной последовательности. Вы можете использовать любой шаблон в шаблоне списка. Чтобы сопоставить любой элемент, используйте шаблон отмены или, если вы также хотите записать элемент, шаблон var, как показано в следующем примере:

List<int> numbers = new() { 1, 2, 3 };

if (numbers is [var first, _, _])
{
    Console.WriteLine($"The first element of a three-item list is {first}.");
}
// Output:
// The first element of a three-item list is 1.

Приведенные выше примеры соответствуют всей входной последовательности по шаблону списка. Чтобы сопоставить элементы только в начале или в конце входной последовательности, используйте шаблон ..среза, как показано в следующем примере:

Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]);  // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]);  // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]);  // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]);  // False

Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]);  // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]);  // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]);  // True

Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]);  // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]);  // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]);  // False

Шаблон среза соответствует нулю или нескольким элементам. В шаблоне списка можно использовать по крайней мере один шаблон среза. Шаблон среза может отображаться только в шаблоне списка.

Вы также можете вложить подпаттерн в шаблон среза, как показано в следующем примере:

void MatchMessage(string message)
{
    var result = message is ['a' or 'A', .. var s, 'a' or 'A']
        ? $"Message {message} matches; inner part is {s}."
        : $"Message {message} doesn't match.";
    Console.WriteLine(result);
}

MatchMessage("aBBA");  // output: Message aBBA matches; inner part is BB.
MatchMessage("apron");  // output: Message apron doesn't match.

void Validate(int[] numbers)
{
    var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid";
    Console.WriteLine(result);
}

Validate(new[] { -1, 0, 1 });  // output: not valid
Validate(new[] { -1, 0, 0, 1 });  // output: valid

Дополнительные сведения см. в заметке о предложении о функциях шаблонов списка.

Спецификация языка C#

Дополнительные сведения см. в разделе "Шаблоны и сопоставление шаблонов" спецификации языка C#.

Сведения о функциях, добавленных в C# 8 и более поздних версиях, см. в следующих заметках о предложении функций:

См. также