Mönstermatchning – uttrycken is och switch operatorerna andoch ornot i mönster

Du använder uttrycket, switch-instruktionenoch switchuttrycketfö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, oroch 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änssnittet Teller så finns det en annan implicit referenskonvertering från den till T. 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örningstyp int[] härleds från Array typen. Vid det andra anropet GetSourceLabel 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 negeratnullkonstantmö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:

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 nullatt 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 negeratnull 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 notkombinatorerna , andoch or mönster för att skapa följande logiska mönster:

  • Negationsmönsternot 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)
    {
        // ...
    }
    
  • Konjunktivtand 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",
    };
    
  • Disjunctiveor 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 ormö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. 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 sektormö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:

Se även