Mönstermatchning – uttrycken is
och switch
operatorerna and
och or
not
i mönster
Du använder uttrycket, switch-instruktionen och switchuttrycket för att matcha ett indatauttryck mot valfritt antal egenskaper.is
C# stöder flera mönster, inklusive deklaration, typ, konstant, relationell, egenskap, lista, var och ignorera. Mönster kan kombineras med booleska logiknyckelord and
, or
och not
.
Följande C#-uttryck och -uttryck stöder mönstermatchning:
I dessa konstruktioner kan du matcha ett indatauttryck mot något av följande mönster:
- Deklarationsmönster: för att kontrollera körningstypen för ett uttryck och, om en matchning lyckas, tilldela ett uttrycksresultat till en deklarerad variabel.
- Typmönster: för att kontrollera körningstypen för ett uttryck.
- Konstant mönster: för att testa om ett uttrycksresultat är lika med en angiven konstant.
- Relationsmönster: för att jämföra ett uttrycksresultat med en angiven konstant.
- Logiska mönster: för att testa om ett uttryck matchar en logisk kombination av mönster.
- Egenskapsmönster: för att testa om ett uttrycks egenskaper eller fält matchar kapslade mönster.
- Positionsmönster: för att dekonstruera ett uttrycksresultat och testa om de resulterande värdena matchar kapslade mönster.
var
mönster: för att matcha alla uttryck och tilldela resultatet till en deklarerad variabel.- Ignorera mönster: för att matcha alla uttryck.
- Listmönster: för att testa om sekvenselement matchar motsvarande kapslade mönster. Introducerades i C# 11.
Mönster för logisk, egenskap, position och lista är rekursiva mönster. De kan alltså innehålla kapslade mönster.
Exempel på hur du använder dessa mönster för att skapa en datadriven algoritm finns i Självstudie: Använda mönstermatchning för att skapa typdrivna och datadrivna algoritmer.
Deklarations- och typmönster
Du använder deklarations- och typmönster för att kontrollera om körningstypen för ett uttryck är kompatibel med en viss typ. Med ett deklarationsmönster kan du också deklarera en ny lokal variabel. När ett deklarationsmönster matchar ett uttryck tilldelas variabeln ett konverterat uttrycksresultat, vilket visas i följande exempel:
object greeting = "Hello, World!";
if (greeting is string message)
{
Console.WriteLine(message.ToLower()); // output: hello, world!
}
Ett deklarationsmönster med typen T
matchar ett uttryck när ett uttrycksresultat inte är null och något av följande villkor är sant:
Körningstypen för ett uttrycksresultat är
T
.Körningstypen för ett uttrycksresultat härleds från typen
T
, implementerar gränssnittetT
eller så finns det en annan implicit referenskonvertering från den tillT
. I följande exempel visas två fall när det här villkoret är sant: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, };
I föregående exempel, vid det första anropet
GetSourceLabel
till metoden, matchar det första mönstret ett argumentvärde eftersom argumentets körningstypint[]
härleds från Array typen. Vid det andra anropetGetSourceLabel
till metoden härleds inte argumentets körningstyp List<T> från Array typen utan implementerar ICollection<T> gränssnittet.Körningstypen för ett uttrycksresultat är en nullbar värdetyp med den underliggande typen
T
.En boxnings - eller avboxningskonvertering finns från körningstypen för ett uttrycksresultat för att skriva
T
.
I följande exempel visas de två sista villkoren:
int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
Console.WriteLine(a + b); // output: 30
}
Om du bara vill kontrollera typen av uttryck kan du använda en ignorera _
i stället för en variabels namn, som följande exempel visar:
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)),
};
}
För det ändamålet kan du använda ett typmönster, som följande exempel visar:
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)),
};
Precis som ett deklarationsmönster matchar ett typmönster ett uttryck när ett uttrycksresultat inte är null och dess körningstyp uppfyller något av de villkor som anges ovan.
Om du vill söka efter icke-null kan du använda ett negerat null
konstantmönster, vilket visas i följande exempel:
if (input is not null)
{
// ...
}
Mer information finns i avsnitten Deklarationsmönster och Typmönster i anteckningarna i funktionsförslaget.
Konstant mönster
Du använder ett konstant mönster för att testa om ett uttrycksresultat är lika med en angiven konstant, vilket visas i följande exempel:
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)),
};
I ett konstant mönster kan du använda alla konstanta uttryck, till exempel:
- ett heltal eller flyttal numerisk literal
- ett tecken
- en strängliteral .
- ett booleskt värde
true
ellerfalse
- ett uppräkningsvärde
- namnet på ett deklarerat const-fält eller lokalt
null
Uttrycket måste vara en typ som kan konverteras till konstanttypen, med ett undantag: Ett uttryck vars typ är Span<char>
eller ReadOnlySpan<char>
kan matchas mot konstanta strängar i C# 11 och senare versioner.
Använd ett konstant mönster för null
att söka efter , som följande exempel visar:
if (input is null)
{
return;
}
Kompilatorn garanterar att ingen användaröverlagrad likhetsoperator ==
anropas när uttrycket x is null
utvärderas.
Du kan använda ett negerat null
konstantmönster för att söka efter icke-null, som följande exempel visar:
if (input is not null)
{
// ...
}
Mer information finns i avsnittet Konstant mönster i funktionsförslagsanteckningen.
Relationsmönster
Du använder ett relationsmönster för att jämföra ett uttrycksresultat med en konstant, vilket visas i följande exempel:
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",
};
I ett relationsmönster kan du använda någon av relationsoperatorerna <
, >
, <=
eller >=
. Den högra delen av ett relationsmönster måste vara ett konstant uttryck. Det konstanta uttrycket kan vara av en heltals-, flyttals-, tecken- eller uppräkningstyp.
Om du vill kontrollera om ett uttrycksresultat ligger i ett visst intervall matchar du det mot ett konjunktivt and
mönster, som följande exempel visar:
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}."),
};
Om ett uttrycksresultat är null
eller misslyckas med att konvertera till typen av en konstant med en nullbar eller avboxningskonvertering matchar inte ett relationsmönster ett uttryck.
Mer information finns i avsnittet Relationsmönster i funktionsförslagsanteckningen.
Logiska mönster
Du använder not
kombinatorerna , and
och or
mönster för att skapa följande logiska mönster:
Negationsmönster
not
som matchar ett uttryck när det negerade mönstret inte matchar uttrycket. I följande exempel visas hur du kan negera ett konstantnull
mönster för att kontrollera om ett uttryck inte är null:if (input is not null) { // ... }
Konjunktivt
and
mönster som matchar ett uttryck när båda mönstren matchar uttrycket. I följande exempel visas hur du kan kombinera relationsmönster för att kontrollera om ett värde finns i ett visst intervall: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", };
Disjunctive
or
mönster som matchar ett uttryck när något av mönstret matchar uttrycket, som följande exempel visar: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}."), };
Som föregående exempel visar kan du upprepade gånger använda mönsterkombinatorerna i ett mönster.
Prioritet och ordning för kontroll
Mönsterkombinatorerna sorteras från den högsta prioriteten till den lägsta enligt följande:
not
and
or
När ett logiskt mönster är ett mönster för ett is
uttryck är prioriteten för logiska mönsterkombinatorer högre än prioriteten för logiska operatorer (både bitvis logiska och booleska logiska operatorer). Annars är prioriteten för logiska mönsterkombinatorer lägre än prioriteten för logiska och villkorsstyrda logiska operatorer. En fullständig lista över C#-operatorer ordnade efter prioritetsnivå finns i avsnittet Operatorprioriteten i artikeln C#-operatorer .
Om du uttryckligen vill ange prioriteten använder du parenteser, som följande exempel visar:
static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');
Kommentar
Den ordning i vilken mönster kontrolleras är odefinierad. Vid körning kan du kontrollera de kapslade mönster or
och and
mönster till höger först.
Mer information finns i avsnittet Mönsterkombinatorer i funktionsförslagsanteckningen.
Egenskapsmönster
Du använder ett egenskapsmönster för att matcha ett uttrycks egenskaper eller fält mot kapslade mönster, som följande exempel visar:
static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };
Ett egenskapsmönster matchar ett uttryck när ett uttrycksresultat inte är null och varje kapslat mönster matchar motsvarande egenskap eller fält i uttrycksresultatet.
Du kan också lägga till en körningstypkontroll och en variabeldeklaration i ett egenskapsmönster, som följande exempel visar:
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."),
};
Ett egenskapsmönster är ett rekursivt mönster. Du kan alltså använda valfritt mönster som ett kapslat mönster. Använd ett egenskapsmönster för att matcha delar av data mot kapslade mönster, som följande exempel visar:
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 } };
I föregående exempel används or
mönsterkombinatorn och posttyperna.
Från och med C# 10 kan du referera till kapslade egenskaper eller fält i ett egenskapsmönster. Den här funktionen kallas för ett utökat egenskapsmönster. Du kan till exempel omstrukturera metoden från föregående exempel till följande motsvarande kod:
static bool IsAnyEndOnXAxis(Segment segment) =>
segment is { Start.Y: 0 } or { End.Y: 0 };
Mer information finns i avsnittet Egenskapsmönster i funktionsförslagsanteckningen och anteckningen Funktionsförslag för utökade egenskapsmönster .
Dricks
Du kan använda stilregeln Förenkla egenskapsmönster (IDE0170) för att förbättra kodens läsbarhet genom att föreslå platser där utökade egenskapsmönster ska användas.
Positionsmönster
Du använder ett positionsmönster för att dekonstruera ett uttrycksresultat och matcha de resulterande värdena mot motsvarande kapslade mönster, som följande exempel visar:
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",
};
I föregående exempel innehåller typen av ett uttryck metoden Deconstruct , som används för att dekonstruera ett uttrycksresultat.
Viktigt!
Ordningen på medlemmar i ett positionsmönster måste matcha ordningen på parametrarna Deconstruct
i metoden. Det beror på att koden som genereras för positionsmönstret anropar Deconstruct
metoden.
Du kan också matcha uttryck av tupplar mot positionsmönster. På så sätt kan du matcha flera indata mot olika mönster, som följande exempel visar:
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,
};
I föregående exempel används relationsmönster och logiska mönster.
Du kan använda namnen på tuppelns element och Deconstruct
parametrar i ett positionsmönster, som följande exempel visar:
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);
}
Du kan också utöka ett positionsmönster på något av följande sätt:
Lägg till en körningstypkontroll och en variabeldeklaration, som följande exempel visar:
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, };
I föregående exempel används positionella poster som implicit tillhandahåller
Deconstruct
metoden.Använd ett egenskapsmönster inom ett positionsmönster, som följande exempel visar:
public record WeightedPoint(int X, int Y) { public double Weight { get; set; } } static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
Kombinera två föregående användningar, som följande exempel visar:
if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p) { // .. }
Ett positionsmönster är ett rekursivt mönster. Du kan alltså använda valfritt mönster som ett kapslat mönster.
Mer information finns i avsnittet Positionellt mönster i funktionsförslagsanteckningen.
var
mönster
Du använder ett var
mönster för att matcha alla uttryck, inklusive null
, och tilldela resultatet till en ny lokal variabel, som följande exempel visar:
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();
}
Ett var
mönster är användbart när du behöver en tillfällig variabel i ett booleskt uttryck för att lagra resultatet av mellanliggande beräkningar. Du kan också använda ett var
mönster när du behöver utföra fler kontroller om vakter för when
ett uttryck eller en switch
-instruktion, som följande exempel visar:
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 }
}
I föregående exempel motsvarar mönstret var (x, y)
ett positionsmönster (var x, var y)
.
I ett var
mönster är typen av en deklarerad variabel kompileringstidstypen för uttrycket som matchas mot mönstret.
Mer information finns i avsnittet Var pattern (Var-mönster ) i funktionsförslagsanteckningen.
Ignorera mönster
Du använder ett ignorerande mönster _
för att matcha alla uttryck, inklusive null
, som följande exempel visar:
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,
};
I föregående exempel används ett ignorerande mönster för att hantera null
och alla heltalsvärden som inte har motsvarande medlem i DayOfWeek uppräkningen. Det garanterar att ett switch
uttryck i exemplet hanterar alla möjliga indatavärden. Om du inte använder ett ignorerande mönster i ett switch
uttryck och inget av uttryckets mönster matchar indata, utlöser körningen ett undantag. Kompilatorn genererar en varning om ett switch
uttryck inte hanterar alla möjliga indatavärden.
Ett ignorerandemönster kan inte vara ett mönster i ett is
uttryck eller en switch
-instruktion. I dessa fall använder du ett var
mönster med ignorerande för att matcha alla uttryck: var _
. Ett ignorerande mönster kan vara ett mönster i ett switch
uttryck.
Mer information finns i avsnittet Ignorera mönster i kommentaren om funktionsförslaget.
Parenteserat mönster
Du kan placera parenteser runt valfritt mönster. Vanligtvis gör du det för att betona eller ändra prioriteten i logiska mönster, som följande exempel visar:
if (input is not (float or double))
{
return;
}
Listmönster
Från och med C# 11 kan du matcha en matris eller en lista mot en sekvens med mönster, som följande exempel visar:
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
Som föregående exempel visar matchas ett listmönster när varje kapslat mönster matchas av motsvarande element i en indatasekvens. Du kan använda valfritt mönster i ett listmönster. Om du vill matcha ett element använder du mönstret ignorera eller, om du också vill avbilda elementet , var-mönstret, som följande exempel visar:
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.
Föregående exempel matchar en hel indatasekvens mot ett listmönster. Om du bara vill matcha element i början eller/och slutet av en indatasekvens använder du segmentmönstret ..
, som följande exempel visar:
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
Ett segmentmönster matchar noll eller fler element. Du kan använda högst ett segmentmönster i ett listmönster. Sektormönstret kan bara visas i ett listmönster.
Du kan också kapsla ett underordnat objekt i ett segmentmönster, vilket visas i följande exempel:
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
Mer information finns i anteckningen Listmönster för funktionsförslag.
Språkspecifikation för C#
Mer information finns i avsnittet Mönster och mönstermatchning i C#-språkspecifikationen.
Information om funktioner som läggs till i C# 8 och senare finns i följande kommentarer om funktionsförslag:
- Rekursiv mönstermatchning
- Mönstermatchningsuppdateringar
- C# 10 – Utökade egenskapsmönster
- C# 11 – Lista mönster
- C# 11 – Mönstermatchning
Span<char>
på strängliteral