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

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

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

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

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

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

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

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

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

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

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

  • Тип результата выражения в среде выполнения — 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)),
    };
}

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

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 не будет вызван перегруженный пользователем оператор равенства ==.

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

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

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

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

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

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, или распаковки-преобразования, то реляционный шаблон не соответствует выражению.

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

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

Начиная с C# 9.0, вы можете использовать блоки объединения not, and и 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

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

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 } };

В предыдущем примере используются две функции, доступные в C# 9.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, используемый для деконструкции результата выражения. Можно также сопоставлять выражения кортежных типов с позиционными шаблонами. Таким образом можно сопоставить несколько входных значений с различными шаблонами, как показано в следующем примере:

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,
    };

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

В позиционном шаблоне можно использовать имена элементов кортежа и параметры метода 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 _.

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

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

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

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#

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

См. также раздел