Patterns (référence C#)

C# a introduit les critères spéciaux en C# 7,0. Depuis, chaque version principale de C# étend les fonctionnalités de critères spéciaux. Les expressions et instructions C# suivantes prennent en charge les critères spéciaux :

Dans ces constructions, vous pouvez faire correspondre une expression d’entrée à l’un des modèles suivants :

  • Modèle de déclaration: pour vérifier le type au moment de l’exécution d’une expression et, si une correspondance est établie, assignez un résultat d’expression à une variable déclarée. Introduit en C# 7,0.
  • Modèle de type: pour vérifier le type au moment de l’exécution d’une expression. Introduit en C# 9,0.
  • Modèle de constante: pour tester si un résultat d’expression est égal à une constante spécifiée. Introduit en C# 7,0.
  • Modèles relationnels: pour comparer un résultat d’expression à une constante spécifiée. Introduit en C# 9,0.
  • Modèles logiques: pour tester si une expression correspond à une combinaison logique de modèles. Introduit en C# 9,0.
  • Modèle de propriété: pour tester si les propriétés ou les champs d’une expression correspondent à des modèles imbriqués. Introduit en C# 8,0.
  • Modèle positionnel: pour déconstruire un résultat d’expression et tester si les valeurs résultantes correspondent à des modèles imbriqués. Introduit en C# 8,0.
  • var pattern: pour faire correspondre n’importe quelle expression et assigner son résultat à une variable déclarée. Introduit en C# 7,0.
  • Modèle d’abandon: pour correspondre à n’importe quelle expression. Introduit en C# 8,0.

Les modèles logiques, de propriétéet de position sont des modèles récursifs . Autrement dit, ils peuvent contenir des modèles imbriqués .

Pour obtenir un exemple d’utilisation de ces modèles pour générer un algorithme piloté par les données, consultez Didacticiel : utiliser des critères spéciaux pour générer des algorithmes pilotés par type et pilotéspar les données.

Modèles de déclaration et de type

Vous utilisez des modèles de déclaration et de type pour vérifier si le type d’exécution d’une expression est compatible avec un type donné. Avec un modèle de déclaration, vous pouvez également déclarer une nouvelle variable locale. Lorsqu’un modèle de déclaration correspond à une expression, une expression convertie est assignée à cette variable, comme le montre l’exemple suivant :

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

À compter de C# 7,0, un modèle de déclaration avec le type T correspond à une expression quand un résultat d’expression n’est pas null et que l’une des conditions suivantes est vraie :

  • Le type au moment de l’exécution d’un résultat d’expression est T .

  • Le type au moment de l’exécution d’un résultat d’expression dérive du type T , implémente l’interface T ou une autre conversion de référence implicite existe à T . L’exemple suivant illustre deux cas lorsque cette condition est vraie :

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

    Dans l’exemple précédent, au premier appel à la GetSourceLabel méthode, le premier modèle correspond à une valeur d’argument parce que le type d’exécution de l’argument int[] dérive du Array type. Au deuxième appel à la GetSourceLabel méthode, le type au moment de l’exécution de l’argument List<T> ne dérive pas du Array type, mais implémente l' ICollection<T> interface.

  • Le type au moment de l’exécution d’un résultat d’expression est un type valeur Nullable avec le type sous-jacent T .

  • Une conversion boxing ou unboxing existe du type au moment de l’exécution d’un résultat d’expression en type T .

L’exemple suivant illustre les deux dernières conditions :

int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
    Console.WriteLine(a + b);  // output: 30
}

Si vous souhaitez vérifier uniquement le type d’une expression, vous pouvez utiliser l’option Ignorer _ à la place du nom d’une variable, comme le montre l’exemple suivant :

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

À compter de C# 9,0, à cette fin, vous pouvez utiliser un modèle de type, comme le montre l’exemple suivant :

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

À l’instar d’un modèle de déclaration, un modèle de type correspond à une expression quand un résultat d’expression est non null et que son type au moment de l’exécution remplit l’une des conditions énumérées ci-dessus.

Pour plus d’informations, consultez les sections modèle de déclaration et modèle de type des notes de la proposition de fonctionnalité.

Modèle de constante

À compter de C# 7,0, vous utilisez un modèle de constante pour tester si un résultat d’expression est égal à une constante spécifiée, comme le montre l’exemple suivant :

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

Dans un modèle de constante, vous pouvez utiliser n’importe quelle expression constante, telle que :

Utilisez un modèle de constante à vérifier null , comme le montre l’exemple suivant :

if (input is null)
{
    return;
}

Le compilateur garantit qu’aucun opérateur d’égalité surchargé par l’utilisateur == n’est appelé lorsque l’expression x is null est évaluée.

À compter de C# 9,0, vous pouvez utiliser un modèle de constante de négation null pour vérifier la non-null, comme le montre l’exemple suivant :

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

Pour plus d’informations, consultez la section modèle de constante de la note de fonctionnalité.

Modèles relationnels

À compter de C# 9,0, vous utilisez un modèle relationnel pour comparer un résultat d’expression à une constante, comme le montre l’exemple suivant :

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

Dans un modèle relationnel, vous pouvez utiliser l’un des opérateurs relationnels < ,, > <= ou >= . La partie droite d’un modèle relationnel doit être une expression constante. L’expression constante peut être de type entier, virgule flottante, caractèreou enum .

Pour vérifier si un résultat d’expression se trouve dans une certaine plage, associez-le à un and modèle conjonctives, comme le montre l’exemple suivant :

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}."),
};

Si le résultat d’une expression est null ou ne parvient pas à convertir le type d’une constante par une conversion Nullable ou unboxing, un modèle relationnel ne correspond pas à une expression.

Pour plus d’informations, consultez la section modèles relationnels de la remarque relative à la proposition de fonctionnalité.

Modèles logiques

À compter de C# 9,0, vous utilisez not les and or combinateurs de modèle, et pour créer les modèles logiques suivants :

  • Négation not modèle qui correspond à une expression lorsque le modèle de négation ne correspond pas à l’expression. L’exemple suivant montre comment vous pouvez null inverser un modèle de constante pour vérifier si une expression n’a pas la valeur NULL :

    if (input is not null)
    {
        // ...
    }
    
  • Conjonctives and modèle qui correspond à une expression lorsque les deux modèles correspondent à l’expression. L’exemple suivant montre comment combiner des modèles relationnels pour vérifier si une valeur est comprise dans une certaine plage :

    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",
    };
    
  • Disjonctive or modèle qui correspond à une expression lorsque l’un des modèles correspond à l’expression, comme le montre l’exemple suivant :

    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}."),
    };
    

Comme le montre l’exemple précédent, vous pouvez utiliser à plusieurs reprises les combinateurs de modèle dans un modèle.

Le and combinateur de modèle a une priorité plus élevée que or . Pour spécifier explicitement la précédence, utilisez des parenthèses, comme le montre l’exemple suivant :

static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');

Notes

L’ordre dans lequel les modèles sont activés n’est pas défini. Au moment de l’exécution, les modèles imbriqués à droite or des and modèles et peuvent être vérifiés en premier.

Pour plus d’informations, consultez la section combinations de modèle de la remarque relative à la proposition de fonctionnalité.

Modèle de propriété

À compter de C# 8,0, vous utilisez un modèle de propriété pour faire correspondre les propriétés ou les champs d’une expression à des modèles imbriqués, comme le montre l’exemple suivant :

static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };

Un modèle de propriété correspond à une expression lorsque le résultat d’une expression est non null et que chaque modèle imbriqué correspond à la propriété ou au champ correspondant du résultat de l’expression.

Vous pouvez également ajouter une vérification de type au moment de l’exécution et une déclaration de variable à un modèle de propriété, comme le montre l’exemple suivant :

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."),
};

Un modèle de propriété est un modèle récursif. Autrement dit, vous pouvez utiliser n’importe quel modèle en tant que modèle imbriqué. Utilisez un modèle de propriété pour faire correspondre des parties de données par rapport à des modèles imbriqués, comme le montre l’exemple suivant :

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

L’exemple précédent utilise deux fonctionnalités disponibles dans C# 9,0 et versions ultérieures : or pattern combin et types d’enregistrement.

À compter de C# 10,0, vous pouvez référencer des propriétés ou des champs imbriqués dans un modèle de propriété. Par exemple, vous pouvez refactoriser la méthode de l’exemple précédent dans le code équivalent suivant :

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start.Y: 0 } or { End.Y: 0 };

Pour plus d’informations, consultez la section modèle de propriété des fonctionnalités note de fonctionnalité et modèles de propriétés étendus .

Modèle positionnel

À compter de C# 8,0, vous utilisez un modèle positionnel pour déconstruire un résultat d’expression et faire correspondre les valeurs obtenues aux modèles imbriqués correspondants, comme le montre l’exemple suivant :

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

Dans l’exemple précédent, le type d’une expression contient la méthode Deconstruct , qui est utilisée pour déconstruire un résultat d’expression. Vous pouvez également faire correspondre des expressions de types tuples à des modèles positionnels. De cette façon, vous pouvez faire correspondre plusieurs entrées à différents modèles, comme le montre l’exemple suivant :

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

L’exemple précédent utilise des modèles relationnels et logiques , qui sont disponibles en C# 9,0 et versions ultérieures.

Vous pouvez utiliser les noms des éléments de tuple et des Deconstruct paramètres dans un modèle positionnel, comme le montre l’exemple suivant :

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

Vous pouvez également étendre un modèle positionnel de l’une des manières suivantes :

  • Ajoutez une vérification de type au moment de l’exécution et une déclaration de variable, comme le montre l’exemple suivant :

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

    L’exemple précédent utilise des enregistrements positionnels qui fournissent implicitement la Deconstruct méthode.

  • Utilisez un modèle de propriété dans un modèle positionnel, comme le montre l’exemple suivant :

    public record WeightedPoint(int X, int Y)
    {
        public double Weight { get; set; }
    }
    
    static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
    
  • Combinez deux utilisations précédentes, comme le montre l’exemple suivant :

    if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p)
    {
        // ..
    }
    

Un modèle positionnel est un modèle récursif. Autrement dit, vous pouvez utiliser n’importe quel modèle en tant que modèle imbriqué.

Pour plus d’informations, consultez la section modèle positionnel de la note de fonctionnalité.

var répétition

À compter de C# 7,0, vous utilisez un var modèle pour faire correspondre n’importe quelle expression, y compris null , et assigner son résultat à une nouvelle variable locale, comme le montre l’exemple suivant :

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

Un var modèle est utile lorsque vous avez besoin d’une variable temporaire dans une expression booléenne pour contenir le résultat de calculs intermédiaires. Vous pouvez également utiliser un var modèle lorsque vous devez effectuer des vérifications supplémentaires dans when les protecteurs de cas d’une switch expression ou d’une instruction, comme le montre l’exemple suivant :

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

Dans l’exemple précédent, pattern var (x, y) est équivalent à un modèle positionnel (var x, var y) .

Dans un var modèle, le type d’une variable déclarée est le type au moment de la compilation de l’expression mise en correspondance avec le modèle.

Pour plus d’informations, consultez la section modèle var de la remarque relative à la proposition de fonctionnalité.

Supprimer le modèle

À compter de C# 8,0, vous utilisez un modèle _ d’annulation pour faire correspondre n’importe quelle expression, y compris null , comme dans l’exemple suivant :

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

Dans l’exemple précédent, un modèle d’abandon est utilisé pour gérer null et toute valeur entière qui n’a pas le membre correspondant de l' DayOfWeek énumération. Cela garantit qu’une switch expression de l’exemple gère toutes les valeurs d’entrée possibles. Si vous n’utilisez pas de modèle d’abandon dans une switch expression et qu’aucun des modèles de l’expression ne correspond à une entrée, le runtime lève une exception. Le compilateur génère un avertissement si une switch expression ne gère pas toutes les valeurs d’entrée possibles.

Un modèle d’abandon ne peut pas être un modèle dans une is expression ou une switch instruction. Dans ce cas, pour correspondre à une expression, utilisez un var modèle avec l’argument Discard : var _ .

Pour plus d’informations, voir la section supprimer le modèle de la remarque relative à la proposition de fonctionnalité.

Modèle entre parenthèses

À compter de C# 9,0, vous pouvez placer des parenthèses autour de n’importe quel modèle. En règle générale, vous pouvez mettre en évidence ou modifier la priorité dans les modèles logiques, comme le montre l’exemple suivant :

if (input is not (float or double))
{
    return;
}

spécification du langage C#

Pour plus d’informations, consultez les notes de la proposition de fonctionnalités suivantes :

Voir aussi