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

Вы используете is выражение, оператор switch и выражение коммутатора для сопоставления входного выражения с любым количеством характеристик. C# поддерживает несколько шаблонов, включая объявление, тип, константу, реляционную, свойство, список, var и dis карта. Шаблоны можно объединить с помощью логических ключевое слово логики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

Если логический шаблон является шаблоном is выражения, приоритет комбинаторов логических шаблонов выше приоритета логических операторов (как побитовых логических, так и логических операторов). В противном случае приоритет объединения логических шаблонов ниже приоритета логических и условных логических операторов. Полный список операторов C#, упорядоченный по уровню приоритета, можно найти в разделе Приоритет операторов статьи Операторы C#.

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

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

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 в примечании к предлагаемой функции.

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

Шаблон dis карта_ используется для сопоставления любого выражения, в том числе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 не обрабатывает все возможные входные значения, компилятор генерирует предупреждение.

Шаблон dis карта не может быть шаблоном is в выражении или инструкцииswitch. В этих случаях для сопоставления выражений используйте шаблонvar с пустой переменной: var _. Шаблон dis карта может быть шаблоном 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

Как показано в предыдущем примере, шаблон списка сопоставляется при сопоставлении каждого вложенного шаблона соответствующим элементом входной последовательности. Вы можете использовать любой шаблон в шаблоне списка. Чтобы сопоставить любой элемент, используйте шаблон dis карта или, если вы также хотите записать элемент, шаблон 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 и более поздних версиях, см. в следующих заметках о предложении функций:

См. также