Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Сопоставление шаблонов —
is
Используйте выражение, оператор switch и выражение коммутатора для сопоставления входного выражения с любым количеством характеристик. C# поддерживает несколько шаблонов, включая объявление, тип, константу, реляционную, свойство, список, вар и отмену. Вы можете объединить шаблоны с помощью логических ключевых слов логики and, orа также not.
Справочные документы по языку C# описывают последнюю выпущенную версию языка C#. Она также содержит начальную документацию по функциям в общедоступных предварительных версиях для предстоящего языкового выпуска.
Документация определяет любую функцию, впервые представленную в последних трех версиях языка или в текущих общедоступных предварительных версиях.
Совет
Чтобы узнать, когда функция впервые появилась в C#, ознакомьтесь со статьей об истории версий языка C#.
Сопоставление шаблонов поддерживают следующие выражения и операторы C#:
В этих конструкциях можно сравнить входное выражение с любым из следующих шаблонов:
- Шаблон объявления: проверьте тип выражения во время его выполнения и, если совпадение успешно, назначьте результат выражения заранее объявленной переменной.
- Шаблон типа: проверьте тип времени выполнения выражения.
- Шаблон констант: проверка того, что результат выражения равен указанной константе.
- Реляционные шаблоны: сравнивайте результат выражения с указанной константой.
- Логические шаблоны: проверка соответствия выражения логическому сочетанию шаблонов.
- Шаблон свойств: проверьте, соответствуют ли свойства или поля выражения вложенным шаблонам.
- Позиционный шаблон: деконструирует результат выражения и проверяет, соответствуют ли полученные значения вложенным шаблонам.
-
varшаблон: сопоставляет любое выражение и назначает результат объявленной переменной. - Шаблон отбрасывания: соответствует любому выражению.
- Шаблоны списка: проверьте, соответствует ли последовательность элементов соответствующим вложенным шаблонам.
Логические, свойства, позиционные и списковые шаблоны являются рекурсивными шаблонами. То есть, они могут содержать вложенные шаблоны.
Пример использования этих шаблонов для создания алгоритма на основе данных см. в руководстве. Использование сопоставления шаблонов для создания алгоритмов, управляемых типом и данными.
Шаблоны объявления и шаблоны типов
Используйте шаблоны объявлений и типов, чтобы проверить, совместим ли тип времени выполнения выражения с заданным типом. С помощью шаблона объявления можно также объявить новую локальную переменную. Когда шаблон объявления соответствует выражению, он назначает переменную преобразованным результату выражения, как показано в следующем примере:
object greeting = "Hello, World!";
if (greeting is string message)
{
Console.WriteLine(message.ToLower()); // output: hello, world!
}
Шаблон объявления с типом T соответствует выражению, если результат выражения не имеет значения NULL, и любое из следующих условий имеет значение true:
- Тип времени выполнения результата выражения имеет идентичное преобразование к
T. - Тип — это
Tтипref struct, и преобразование удостоверений из выражения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и типом Nullable<T>.HasValuetrue. -
Преобразование упаковки или распаковки существует из типа результата выражения во время выполнения в тип
T, если выражение не является экземпляромref struct.
Шаблоны объявлений не учитывают определяемые пользователем преобразования или неявные преобразования диапазона.
В следующем примере показаны два последних условия:
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)
{
// ...
}
Дополнительные сведения см. в разделах шаблонов объявления и шаблонов типов спецификации языка C#.
Шаблон константы
Шаблон константы является альтернативным синтаксисом, если == правый операнд является константой. Используйте шаблон константы , чтобы проверить, равен ли результат выражения заданной константы, как показано в следующем примере:
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)),
};
В шаблоне константы можно использовать любое константное выражение, например:
- целочисленный литерал или литерал с плавающей точкой;
- char
- строковый литерал
- логическое значение
trueилиfalse; - значение типа enum;
- имя объявленного поля с модификатором const или локального элемента.
null
Выражение должно быть типом, который преобразуется в тип константы, за исключением выражения, тип которого совпадает Span<char>ReadOnlySpan<char> с константными строками.
Используйте шаблон константы для проверки на 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 или распаковки, реляционный шаблон не соответствует выражению.
Дополнительные сведения см. в разделе " Реляционные шаблоны" спецификации языка C#.
Логические шаблоны
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}."), };
Как показано в предыдущем примере, можно многократно использовать комбинаторы шаблонов в шаблоне.
Приоритет и порядок проверки
Комбинаторы шаблонов проверяют выражения в этом порядке на основе порядка привязки выражений:
notandor
Шаблон not сначала привязывается к операнду. Шаблон and привязывается после любой not привязки выражения шаблона. Шаблон or привязывается после всех not и and шаблонов привязывается к операндам. В следующем примере показано, как сопоставить все символы, которые не являются строчными буквами. az Она имеет ошибку, так как 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 шаблонов.
Дополнительные сведения см. в разделе "Комбинаторы шаблонов " спецификации языка C#.
Шаблон свойства
Используйте шаблон свойства для сопоставления свойств или полей выражения с вложенными шаблонами, как показано в следующем примере:
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."),
};
Эта конструкция специально означает, что шаблон is { } свойства соответствует всему ненулевому значению, и его можно использовать вместо is not null создания переменной: somethingPossiblyNull is { } somethingDefinitelyNotNull
if (GetSomeNullableStringValue() is { } nonNullValue) // Empty property pattern with variable creation
{
Console.WriteLine("NotNull:" + nonNullValue);
}
else
{
nonNullValue = "NullFallback"; // we can access the variable here.
Console.WriteLine("it was null, here's the fallback: " + nonNullValue);
}
Шаблон свойства является рекурсивным шаблоном. Вы можете использовать любой шаблон как вложенный шаблон. Используйте шаблон свойства для сопоставления элементов данных с вложенными шаблонами, как показано в следующем примере:
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комбинатор шаблонов и типы записей.
В шаблоне свойств можно ссылаться на вложенные свойства или поля. Эта возможность называется шаблоном расширенных свойств. Например, можно выполнить рефакторинг метода из предыдущего примера в следующий эквивалентный код:
static bool IsAnyEndOnXAxis(Segment segment) =>
segment is { Start.Y: 0 } or { End.Y: 0 };
Дополнительные сведения см. в разделе "Шаблон свойств " стандарта C#.
Совет
Чтобы улучшить удобочитаемость кода, используйте правило стиля "Упрощение свойств" (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 метод.
Можно также сопоставлять выражения кортежных типов с позиционными шаблонами. С помощью этого подхода можно сопоставить несколько входных данных с различными шаблонами, как показано в следующем примере:
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;
}
Шаблоны списков
Вы можете сопоставить массив или список с последовательностью шаблонов, как показано в следующем примере:
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# 15, выражение, switch определяющее тип closed класса является исчерпывающим , когда его руки обрабатывают каждый прямой потомок этого класса. Компилятору не требуется руку по умолчанию, так как коммутатор является исчерпывающим:
public closed record class PaymentMethod;
public record class Cash : PaymentMethod;
public record class Card(string Last4) : PaymentMethod;
public record class BankTransfer(string Iban) : PaymentMethod;
public static string Describe(PaymentMethod method) => method switch
{
Cash => "cash",
Card(var last4) => $"card ending {last4}",
BankTransfer(var iban) => $"bank transfer to {iban}",
// No warning: every direct descendant of 'PaymentMethod' is handled.
};
Закрытый коммутатор иерархии является исчерпывающим, только если каждый прямой потомок доступен из расположения коммутатора. Если прямой потомок менее доступен, чем закрытый базовый тип и не отображается на сайте коммутатора, компилятор обрабатывает его как необработанные и предупреждает, что переключатель не является исчерпывающим.
Например, закрытый public базовый internal класс может иметь прямой потомок. Код в той же сборке видит полный набор потомков, но код в другой сборке не содержит:
// Assembly 1
public closed record class Shape;
public record class Circle(double Radius) : Shape;
internal record class Triangle(double Base, double Height) : Shape;
// Same assembly: 'Triangle' is visible, so the switch is exhaustive.
internal static double Area(Shape shape) => shape switch
{
Circle(var r) => Math.PI * r * r,
Triangle(var b, var h) => 0.5 * b * h,
};
// Assembly 2
public static string Name(Shape shape) => shape switch
{
Circle => "circle",
// Warning CS8509: the switch expression doesn't handle all possible values.
// 'Triangle' is a direct descendant of 'Shape' but isn't visible here.
};
Чтобы восстановить исчерпывающую степень в сборке 2, добавьте руку отмены (), добавьте руку базового класса (_ => ...Shapeв предыдущем примере) или сделайте каждый прямой потомок по крайней мере доступным как закрытый базовый тип.
Если управляющий тип имеет значение NULL, это дополнительное значение, null которое должен обрабатывать коммутатор. Переключение на PaymentMethod? это опустит null руку не является исчерпывающим даже при сопоставлении каждого прямого потомка.
Производный от закрытого класса не является транзитивным: не закрытый потомок закрытого класса может быть производным от других сборок. Компилятор обрабатывает только прямые потомки как исчерпывающий набор. Чтобы переключиться на потомок, также воспользоваться исчерпывающей проверкой, объявите потомка closed (или sealed).
Так как косвенные потомки не расширяют исчерпывающий набор закрытой базы, вам не нужно добавлять руку для каждого транзитивного подтипа для удовлетворения исчерпывающего значения. Подчинение между руками по-прежнему работает так же, как и для любой иерархии классов: рука, которая соответствует базовому типу, охватывает каждый подтип, а более поздняя рука, соответствующая одному из этих подтипов, недоступна. Рассмотрим закрытыйVehicle, прямые потомки которого и Truck, плюс косвенный потомок CarSedan, объявленный в другой сборке:
// Assembly 1
public closed record class Vehicle;
public record class Car(int Doors) : Vehicle;
public record class Truck(double PayloadTons) : Vehicle;
// Assembly 2
public record class Sedan(int Doors) : Car(Doors);
Переключение Vehicle является исчерпывающим после обработки Car и Truck, хотя Sedan и существует. Рука охватывает каждое CarSedan значение:
public static string Category(Vehicle v) => v switch
{
Car => "car",
Truck => "truck",
// No warning. The 'Car' arm covers 'Sedan' through ordinary subtype matching.
};
Чтобы отправить в частности, поместите Sedan руку перед рукой Car . Рука остается доступной Car , потому что она по-прежнему соответствует каждому Car , что не Sedanявляется:
public static string CategorySedanFirst(Vehicle v) => v switch
{
Sedan => "sedan",
Car => "car", // Reachable: 'Car' values that aren't 'Sedan'.
Truck => "truck",
};
Отмена этих двух рук приводит к ошибке подсупления, так же как и в любой другой иерархии классов. Компилятор обнаруживает, что Car рука уже охватывает Sedan:
string Category(Vehicle v) => v switch
{
Car => "car",
Sedan => "sedan", // Error CS8510: the pattern is unreachable. It has already been handled by 'Car => ...'.
Truck => "truck",
};
Если требуется исчерпывающее выполнение иерархии дальше вниз, объявите Car себя closed. Затем компилятор обрабатывает каждый прямой потомок Car (например Sedan, ) как исчерпывающий набор, коренящийся в Car. Переключатель, управляющий тип Car которого является исчерпывающим при выполнении любого из следующих действий:
- Обрабатывает каждый прямой потомок с собственной рукой
Car. - Включает руку, которая охватывает каждое
CarCarзначение (включая все подтипы). - Включает в себя отбрасываемую руку (
_ => ...) или руку для базового типаCar, напримерVehicle.
Переключатель, управляющий тип которого имеет Vehicle выбор: код, который обрабатывает Car и Truck по-прежнему является исчерпывающим, так как Car рука охватывает каждый подтип Car. Маркировка Carclosed просто дает второй вариант для этого коммутатора. Вы можете сохранить одну Car руку или заменить ее одним рукой на прямой потомок Car (наряду с рукой Truck ) и по-прежнему быть исчерпывающим.
Маркировка Carclosed также делает его неявным abstractобразом, что означает, что вы больше не можете создавать экземпляры Car напрямую. Это может не соответствовать вашему дизайну. Если вам нужно Car оставаться в состоянии создания экземпляров, оставьте его открытым и отправленным по определенным подтипам, которые вы заботитесь о заказе оружия, как показано ранее.
Тип параметров, управляющих типами
switch Выражение, управляющий типом которого является параметр типа, ограниченный закрытым классом, является исчерпывающим для тех же терминов, что и переключение самого закрытого класса. Чтобы отправить в закрытую иерархию из универсального кода, необходимо ограничить параметр типа закрытой базой и обрабатывать каждый прямой потомок:
public static string Describe<X>(X method) where X : PaymentMethod => method switch
{
Cash => "cash",
Card(var last4) => $"card ending {last4}",
BankTransfer(var iban) => $"bank transfer to {iban}",
// No warning: 'X' is constrained to the closed type 'PaymentMethod',
// so handling every direct descendant exhausts the switch.
};
Дополнительные сведения см. в закрытом модификаторе. Сведения о спецификации см. в разделе "Закрытые иерархии".
Шаблоны объединения
Начиная с C# 15, когда входящее значение шаблона является типом объединения, шаблоны автоматически распаковывают объединение. Они применяются к свойству профсоюза Value , а не самой ценности объединения. Это поведение делает объединение прозрачным для сопоставления шаблонов:
public record class Cat(string Name);
public record class Dog(string Name);
public union Pet(Cat, Dog);
string Describe(Pet pet) => pet switch
{
Dog d => d.Name,
Cat c => c.Name,
};
Два шаблона являются исключениями: var шаблон и шаблон отмены _ применяются к самому значению объединения, а не к его Value свойству.
Шаблон null проверяет, имеет ли объединение Value значение NULL. Для профсоюзов на основе классов также выполняется успешно, null когда ссылка на объединение имеет значение NULL.
Если тип объединения предоставляет шаблон доступа без бокса (HasValue и TryGetValue члены), компилятор использует эти элементы, чтобы избежать боксовых вариантов типа значений во время сопоставления шаблонов.
Дополнительные сведения см. в разделе "Сопоставление союзов". Спецификация см. в разделе "Союзы".
Спецификация языка C#
Дополнительные сведения см. в разделе "Шаблоны и сопоставление шаблонов " спецификации языка C#, в том числе: