Zdieľať cez


11 Patterns and pattern matching

11.1 General

A pattern is a syntactic form that can be used with the is operator (§12.15.12), in a switch_statement (§13.8.3), and in a switch_expression (§12.12) to express the shape of data against which incoming data is to be compared. Patterns may be recursive, so that parts of the data may be matched against sub-patterns.

A pattern is tested against a value in a number of contexts:

  • In a switch statement, the pattern of a case label is tested against the expression of the switch statement.
  • In an is-pattern operator, the pattern on the right-hand-side is tested against the expression on the left.
  • In a switch expression, the pattern of a switch_expression_arm is tested against the expression on the switch-expression’s left-hand-side.
  • In nested contexts, the sub-pattern is tested against values retrieved from properties, fields, or indexed from other input values, depending on the pattern form.

The value against which a pattern is tested is called the pattern input value.

11.2 Pattern forms

11.2.1 General

A pattern may have one of the following forms:

pattern
    : logical_pattern
    ;

primary_pattern
    : parenthesized_pattern
    | declaration_pattern
    | constant_pattern
    | var_pattern
    | positional_pattern
    | property_pattern
    | discard_pattern
    | type_pattern
    | relational_pattern
    ;

parenthesized_pattern
    : '(' pattern ')'
    ;

The '(' pattern ')' production allows a pattern to be enclosed in parentheses to enforce the order of evaluation among patterns combined using one of the logical_patterns.

Some patterns can result in the declaration of a local variable.

Each pattern form defines the set of types for input values that the pattern may be applied to. A pattern P is applicable to a type T if T is among the types whose values the pattern may match. It is a compile-time error if a pattern P appears in a program to match a pattern input value (§11.1) of type T if P is not applicable to T.

Example: The following example generates a compile-time error because the compile-time type of v is TextReader. A variable of type TextReader can never have a value that is reference-compatible with string:

TextReader v = Console.In; // compile-time type of 'v' is 'TextReader'
if (v is string) // compile-time error
{
    // code assuming v is a string
}

However, the following does not generate a compile-time error because the compile-time type of v is object. A variable of type object could have a value that is reference-compatible with string:

object v = Console.In;
if (v is string s)
{
    // code assuming v is a string
}

end example

Each pattern form defines the set of values for which the pattern matches the value at runtime.

The order of evaluation of operations and side effects during pattern-matching (calls to Deconstruct, property accesses, and invocations of methods in System.ITuple) is not specified.

11.2.2 Declaration pattern

A declaration_pattern is used to test that a value has a given type and, if the test succeeds, to optionally provide the value in a variable of that type.

declaration_pattern
    : type simple_designation
    ;
simple_designation
    : discard_designation
    | single_variable_designation
    ;
discard_designation
    : '_'
    ;
single_variable_designation
    : identifier
    ;

A simple_designation with the token _ shall be considered a discard_designation rather than a single_variable_designation.

The runtime type of the value is tested against the type in the pattern using the same rules specified in the is-type operator (§12.15.12.1). If the test succeeds, the pattern matches that value. It is a compile-time error if the type is a nullable value type (§8.3.12) or a nullable reference type (§8.9.3). This pattern form never matches a null value.

Note: The is-type expression e is T and the declaration pattern e is T _ are equivalent when T is not a nullable type. end note

Given a pattern input value (§11.1) e, if the simple_designation is discard_designation, it denotes a discard (§9.2.9.2), and the value of e is not bound to anything. (Although a declared variable with the name _ may be in scope at that point, that named variable is not seen in this context.) Otherwise, if the simple_designation is single_variable_designation, a local variable (§9.2.9) of the given type named by the given identifier is introduced. That local variable is assigned the value of the pattern input value when the pattern matches the value.

Certain combinations of static type of the pattern input value and the given type are considered incompatible and result in a compile-time error. A value of static type E is said to be pattern compatible with the type T if there exists an identity conversion, an implicit or explicit reference conversion, a boxing conversion, an unboxing conversion, or an implicit or explicit nullable value type conversion from E to T, or if either E or T is an open type (§8.4.3). A declaration pattern naming a type T is applicable to every type E for which E is pattern compatible with T.

Note: The support for open types can be most useful when checking types that may be either struct or class types, and boxing is to be avoided. end note

Example: The declaration pattern is useful for performing run-time type tests of reference types, and replaces the idiom

var v = expr as Type;
if (v != null) { /* code using v */ }

with the slightly more concise

if (expr is Type v) { /* code using v */ }

end example

Example: The declaration pattern can be used to test values of nullable types: a value of type Nullable<T> (or a boxed T) matches a type pattern T2 id if the value is non-null and T2 is T, or some base type or interface of T. For example, in the code fragment

int? x = 3;
if (x is int v) { /* code using v */ }

The condition of the if statement is true at runtime and the variable v holds the value 3 of type int inside the block. After the block the variable v is in scope, but not definitely assigned. end example

11.2.3 Constant pattern

A constant_pattern is used to test the value of a pattern input value (§11.1) against the given constant value.

constant_pattern
    : constant_expression
    ;

A constant pattern P is applicable to a type T if there is an implicit conversion from the constant expression of P to the type T.

For a constant pattern P, its converted value is

  • if the pattern input value’s type is an integral type or an enum type, the pattern’s constant value converted to that type; otherwise
  • if the pattern input value’s type is the nullable version of an integral type or an enum type, the pattern’s constant value converted to its underlying type; otherwise
  • the value of the pattern’s constant value.

Given a pattern input value e and a constant pattern P with converted value v,

  • if e has integral type or enum type, or a nullable form of one of those, and v has integral type, the pattern P matches the value e if result of the expression e == v is true; otherwise
  • the pattern P matches the value e if object.Equals(e, v) returns true.

Example: The switch statement in the following method uses five constant patterns in its case labels.

static decimal GetGroupTicketPrice(int visitorCount)
{
    switch (visitorCount) 
    {
        case 1: return 12.0m;
        case 2: return 20.0m;
        case 3: return 27.0m;
        case 4: return 32.0m;
        case 0: return 0.0m;
        default: throw new ArgumentException(...);
    }
}

end example

11.2.4 Var pattern

A var_pattern matches every value. That is, a pattern-matching operation with a var_pattern always succeeds.

A var_pattern is applicable to every type.

var_pattern
    : 'var' designation
    ;
designation
    : simple_designation
    | tuple_designation
    ;
tuple_designation
    : '(' designations? ')'
    ;
designations
    : designation (',' designation)*
    ;

Given a pattern input value (§11.1) e, if designation is discard_designation, it denotes a discard (§9.2.9.2), and the value of e is not bound to anything. (Although a declared variable with that name may be in scope at that point, that named variable is not seen in this context.) Otherwise, if designation is single_variable_designation, at runtime the value of e is bound to a newly introduced local variable (§9.2.9) of that name whose type is the static type of e, and the pattern input value is assigned to that local variable.

It is an error if the name var would bind to a type where a var_pattern is used.

If designation is a tuple_designation, the pattern is equivalent to a positional_pattern (§11.2.5) of the form (var designation, … ) where the designations are those found within the tuple_designation. For example, the pattern var (x, (y, z)) is equivalent to (var x, (var y, var z)).

11.2.5 Positional pattern

A positional_pattern checks that the input value is not null, invokes an appropriate Deconstruct method (§12.7), and performs further pattern matching on the resulting values. It also supports a tuple-like pattern syntax (without the type being provided) when the type of the input value is the same as the type containing Deconstruct, or if the type of the input value is a tuple type, or if the type of the input value is object or System.ITuple and the runtime type of the expression implements System.ITuple.

positional_pattern
    : type? '(' subpatterns? ')' property_subpattern? simple_designation?
    ;
subpatterns
    : subpattern (',' subpattern)*
    ;
subpattern
    : pattern
    | identifier ':' pattern
    ;

Given a match of an input value to the pattern type ( subpatterns ), a method is selected by searching in type for accessible declarations of Deconstruct and selecting one among them using the same rules as for the deconstruction declaration. It is an error if a positional_pattern omits the type, has a single subpattern without an identifier, has no property_subpattern and has no simple_designation. This disambiguates between a constant_pattern that is parenthesized and a positional_pattern. In order to extract the values to match against the patterns in the list,

  • If type is omitted and the input expression’s type is a tuple type, then the number of subpatterns shall to be the same as the cardinality of the tuple. Each tuple element is matched against the corresponding subpattern, and the match succeeds if all of these succeed. If any subpattern has an identifier, then that shall name a tuple element at the corresponding position in the tuple type.
  • Otherwise, if a suitable Deconstruct exists as a member of type, it is a compile-time error if the type of the input value is not pattern-compatible with type. At runtime the input value is tested against type. If this fails, then the positional pattern match fails. If it succeeds, the input value is converted to this type and Deconstruct is invoked with fresh compiler-generated variables to receive the output parameters. Each value that was received is matched against the corresponding subpattern, and the match succeeds if all of these succeed. If any subpattern has an identifier, then that shall name a parameter at the corresponding position of Deconstruct.
  • Otherwise, if type is omitted, and the input value is of type object or some type that can be converted to System.ITuple by an implicit reference conversion, and no identifier appears among the subpatterns, then the match uses System.ITuple.
  • Otherwise, the pattern is a compile-time error.

The order in which subpatterns are matched at runtime is unspecified, and a failed match might not attempt to match all subpatterns.

Example: Here, we deconstruct an expression result and match the resulting values against the corresponding nested patterns:

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

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

end example

Example: The names of tuple elements and Deconstruct parameters can be used in a positional pattern, as follows:

var numbers = new List<int> { 10, 20, 30 };
if (SumAndCount(numbers) is (Sum: var sum, Count: var count))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");
}

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

The output produced is

Sum of [10 20 30] is 60

end example

11.2.6 Property pattern

A property_pattern checks that the input value is not null, and recursively matches values extracted by the use of accessible properties or fields.

property_pattern
    : type? property_subpattern simple_designation?
    ;
property_subpattern
    : '{' '}'
    | '{' subpatterns ','? '}'
    ;

It is an error if any subpattern of a property_pattern does not contain an identifier.

It is a compile-time error if the type is a nullable value type (§8.3.12) or a nullable reference type (§8.9.3).

Note: A null-checking pattern falls out of a trivial property pattern. To check if the string s is non-null, one can write any of the following forms:

#nullable enable
string s = "abc";
if (s is object o) ...  // o is of type object
if (s is string x1) ... // x1 is of type string
if (s is {} x2) ...     // x2 is of type string
if (s is {}) ...

end note

Given a match of an expression e to the pattern type { subpatterns }, it is a compile-time error if the expression e is not pattern-compatible with the type T designated by type. If the type is absent, the type is assumed to be the static type of e. Each of the identifiers appearing on the left-hand-side of its subpatterns shall designate an accessible readable property or field of T. If the simple_designation of the property_pattern is present, it declares a pattern variable of type T.

At runtime, the expression is tested against T. If this fails then the property pattern match fails, and the result is false. If it succeeds, then each property_subpattern field or property is read, and its value matched against its corresponding pattern. The result of the whole match is false only if the result of any of these is false. The order in which subpatterns are matched is not specified, and a failed match may not test all subpatterns at runtime. If the match succeeds and the simple_designation of the property_pattern is a single_variable_designation, the declared variable is assigned the matched value.

The property_pattern may be used to pattern-match with anonymous types.

Example:

var o = ...;
if (o is string { Length: 5 } s) ...

end example

Example: A run-time type check and a variable declaration can be added to a property pattern, as follow:

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

The output produced is

Hello
Hi!
12345
abc

end example

11.2.7 Discard pattern

Every expression matches the discard pattern, which results in the value of the expression being discarded.

discard_pattern
    : '_'
    ;

It is a compile-time error to use a discard pattern in a relational_expression of the form relational_expression is pattern or as the pattern of a switch_label.

Note: In those cases, to match any expression, use a var_pattern with a discard var _. end note

Example:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));
Console.WriteLine(GetDiscountInPercent(null));
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));

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

The output produced is

5.0
0.0
0.0

Here, a discard pattern is used to handle null and any integer value that does not have the corresponding member of the DayOfWeek enumeration. That guarantees that the switch expression handles all possible input values. end example

11.2.8 Type pattern

A type_pattern is used to test that the pattern input value (§11.1) has a given type.

type_pattern
    : type
    ;

A type pattern naming a type T is applicable to every type E for which E is pattern compatible with T (§11.2.2).

The runtime type of the value is tested against type using the same rules specified in the is-type operator (§12.15.12.1). If the test succeeds, the pattern matches that value. It is a compile-time error if the type is a nullable type. This pattern form never matches a null value.

11.2.9 Relational pattern

A relational_pattern is used to relationally test the pattern input value (§11.1) against a constant value.

relational_pattern
    : '<'  relational_expression
    | '<=' relational_expression
    | '>'  relational_expression
    | '>=' relational_expression
    ;

The relational_expression in a relational_pattern is required to evaluate to a constant value.

Relational patterns support the relational operators <, <=, >, and >= on all of the built-in types that support such binary relational operators with both operands having the same type: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, nint, nuint, and enums.

A relational_pattern is applicable to a type T if a suitable built-in binary relational operator is defined with both operands of type T, or if an explicit nullable or unboxing conversion exists from T to the type of the constant expression.

It is a compile-time error if the expression evaluates to double.NaN, float.NaN, or a null constant.

When the input value has a type for which a suitable built-in binary relational operator is defined, the evaluation of that operator is taken as the meaning of the relational pattern. Otherwise, the input value is converted to the type of the constant expression using an explicit nullable or unboxing conversion. It is a compile-time error if no such conversion exists. The pattern is considered to not match if the conversion fails. If the conversion succeeds, the result of the pattern-matching operation is the result of evaluating the expression e «op» v where e is the converted input, «op» is the relational operator, and v is the constant expression.

Example:

Console.WriteLine(Classify(13));
Console.WriteLine(Classify(double.NaN));
Console.WriteLine(Classify(2.4));

static string Classify(double measurement) => measurement switch
{
    < -4.0 => "Too low",
    > 10.0 => "Too high",
    double.NaN => "Unknown",
    _ => "Acceptable",
};

The output produced is

Too high
Unknown
Acceptable

end example

11.2.10 Logical pattern

A logical_pattern is used to negate the result of a pattern match, or to combine the results of multiple pattern matches using conjunction (and) or disjunction (or).

logical_pattern
    : disjunctive_pattern
    ;

disjunctive_pattern
    : disjunctive_pattern 'or' conjunctive_pattern
    | conjunctive_pattern
    ;

conjunctive_pattern
    : conjunctive_pattern 'and' negated_pattern
    | negated_pattern
    ;

negated_pattern
    : 'not' negated_pattern
    | primary_pattern
    ;

not, and, and or are collectively called pattern operators.

A negated_pattern matches if the pattern being negated does not match, and vice versa. A conjunctive_pattern requires both patterns to match. A disjunctive_pattern requires either pattern to match. Unlike their language operator counterparts, && and ||, and and or are not short-circuiting operators.

It is a compile-time error for a pattern variable to be declared beneath a not or or pattern operator.

Note: Because neither not nor or can produce a definite assignment for a pattern variable, it is an error to declare one in those positions. end note

In a conjunctive_pattern, the input type of the second pattern is narrowed by the type narrowing requirements of first pattern of the and. The narrowed type of a pattern P is defined as follows:

  • If P is a type pattern, the narrowed type is the type of the type pattern's type.
  • Otherwise, if P is a declaration pattern, the narrowed type is the type of the declaration pattern's type.
  • Otherwise, if P is a recursive pattern that gives an explicit type, the narrowed type is that type.
  • Otherwise, if P is matched via the rules for ITuple in a positional_pattern (§11.2.5), the narrowed type is the type System.ITuple.
  • Otherwise, if P is a constant pattern where the constant is not the null constant and where the expression has no constant expression conversion to the input type, the narrowed type is the type of the constant.
  • Otherwise, if P is a relational pattern where the constant expression has no constant expression conversion to the input type, the narrowed type is the type of the constant.
  • Otherwise, if P is an or pattern, the narrowed type is the common type of the narrowed type of the subpatterns if such a common type exists. For this purpose, the common type algorithm considers only identity, boxing, and implicit reference conversions, and it considers all subpatterns of a sequence of or patterns (ignoring parenthesized patterns).
  • Otherwise, if P is an and pattern, the narrowed type is the narrowed type of the right pattern. Moreover, the narrowed type of the left pattern is the input type of the right pattern.
  • Otherwise the narrowed type of P is P's input type.

Note: As indicated by the grammar, not has precedence over and, which has precedence over or. This can be explicitly indicated or overridden by using parentheses. end note

When a pattern appears on the right-hand-side of is, the extent of the pattern is determined by the grammar; as a result, the pattern operators and, or, and not within the pattern bind more tightly than the logical operators &&, ||, and ! outside the pattern.

Example:

Console.WriteLine(Classify(13));
Console.WriteLine(Classify(-100));
Console.WriteLine(Classify(5.7));

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

The output produced is

High
Too low
Acceptable

end example

Example:

Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));

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

The output produced is

winter
autumn
spring

end example

Example:

object msg = "msg";
object obj = 5;
bool flag = true;

// This is parsed as: (msg is (not int) or string)
result = msg is not int or string;
Console.WriteLine($"msg (\"msg\"): msg is not int or string: {result}");

// This is parsed as: (obj is (int or string)) && flag
bool result = obj is int or string && flag;
Console.WriteLine($"obj (5), flag (true): obj is int or string && flag: {result}");

// This is parsed as: (obj is int) || ((obj is string) && flag)
result = obj is int || obj is string && flag;
Console.WriteLine($"obj (5), flag (true): obj is int || obj is string && flag: {result}");

flag = false;
// This is parsed as: (obj is (int or string)) && flag
result = obj is int or string && flag;
Console.WriteLine($"obj (5), flag (false): obj is int or string && flag: {result}");

// This is parsed as: (obj is int) || ((obj is string) && flag)
result = obj is int || obj is string && flag;
Console.WriteLine($"obj (5), flag (false): obj is int || obj is string && flag: {result}");

The output produced is

msg ("msg"): msg is not int or string: True
obj (5), flag (true): obj is int or string && flag: True
obj (5), flag (true): obj is int || obj is string && flag: True
obj (5), flag (false): obj is int or string && flag: False
obj (5), flag (false): obj is int || obj is string && flag: True

end example

11.3 Pattern subsumption

In a switch statement, it is an error if a case’s pattern is subsumed by the preceding set of unguarded cases (§13.8.3). Informally, this means that any input value would have been matched by one of the previous cases. The following rules define when a set of patterns subsumes a given pattern:

A pattern P would match a constant K if any of the following conditions hold:

  • the specification for that pattern's runtime behavior is that P matches K.
  • P is a type_pattern for type T and K is not null and the runtime type of K is T or a type derived from T or a type that implements T.
  • P is a relational_pattern with operator «op» and constant v, and the expression K «op» v would evaluate to true.
  • P is a negated_pattern not P₁ and P₁ would not match K.
  • P is a conjunctive_pattern P₁ and P₂ and both P₁ would match K and P₂ would match K.
  • P is a disjunctive_pattern P₁ or P₂ and either P₁ would match K or P₂ would match K.
  • P is a discard_pattern.

A set of patterns Q subsumes a pattern P if any of the following conditions hold:

  • P is a constant pattern and any of the patterns in the set Q would match P's converted value
  • P is a var pattern and the set of patterns Q is exhaustive (§11.4) for the type of the pattern input value (§11.1), and either the pattern input value is not of a nullable type or some pattern in Q would match null.
  • P is a declaration pattern with type T and the set of patterns Q is exhaustive for the type T (§11.4).
  • P is a type_pattern for type T and the set of patterns Q is exhaustive for the type T.
  • P is a relational_pattern with operator «op» and constant value v, and for every value of the input type satisfying the relation «op» v, some pattern in the set Q would match that value.
  • P is a disjunctive_pattern P₁ or P₂ and the set of patterns Q subsumes P₁ and Q subsumes P₂.
  • P is a conjunctive_pattern P₁ and P₂ and at least one of the following holds: Q subsumes P₁, or Q subsumes P₂.
  • P is a negated_pattern not P₁ and Q is exhaustive for the input type considering only the values not matched by P₁.
  • P is a discard_pattern and the set of patterns Q is exhaustive for the type of the pattern input value, and either the pattern input value is not of a nullable type or some pattern in Q would match null.
  • Some pattern in Q is a disjunctive_pattern Q₁ or Q₂ and replacing that pattern with Q₁ in Q would yield a set that subsumes P, or replacing it with Q₂ would yield a set that subsumes P.
  • Some pattern in Q is a negated_pattern not Q₁ and P would not match any value that Q₁ would match.

11.4 Pattern exhaustiveness

Informally, a set of patterns is exhaustive for a type if, for every possible value of that type other than null, some pattern in the set is applicable. The following rules define when a set of patterns is exhaustive for a type:

A set of patterns Q is exhaustive for a type T if any of the following conditions hold:

  1. T is an integral or enum type, or a nullable version of one of those, and for every possible value of T’s non-nullable underlying type, some pattern in Q would match that value; or
  2. Some pattern in Q is a var pattern; or
  3. Some pattern in Q is a declaration pattern for type D, and there is an identity conversion, an implicit reference conversion, or a boxing conversion from T to D; or
  4. Some pattern in Q is a type_pattern for type D, and there is an identity conversion, an implicit reference conversion, or a boxing conversion from T to D; or
  5. Some pattern in Q is a discard_pattern; or
  6. The patterns in Q include a combination of relational_patterns and constant_patterns whose ranges collectively cover every possible value of T's non-nullable underlying type. For float and double types, this includes System.Double.NaN or System.Single.NaN respectively, since NaN is not matched by any relational pattern; or
  7. Some pattern in Q is a disjunctive_pattern P₁ or P₂, and replacing that pattern with both P₁ and P₂ in Q yields a set that is exhaustive for T; or
  8. Some pattern in Q is a negated_pattern not P₁, and the patterns in Q together with the values not matched by P₁ cover every possible value of T. A negated_pattern not P₁ is exhaustive by itself when P₁ matches no possible value of T; or
  9. Some pattern in Q is a conjunctive_pattern P₁ and P₂, and the set containing only P₁ is exhaustive for T and the set containing only P₂ is exhaustive for T.

Note: When a type pattern includes nullable types, the pattern may be exhaustive for the type but still generate a warning because the type pattern won't match a null value. end note

Note: For floating-point types, the combination of patterns < 0 and >= 0 is not exhaustive because neither relational pattern matches NaN. A correct exhaustive set would be < 0, >= 0, and double.NaN (or float.NaN). end note

Example:

static void M(byte b)
{
    switch (b) {
        case 0: case 1: case 2: ... // handle every specific value of byte
            break;
        // error: the pattern 'byte other' is subsumed by the (exhaustive)
        // previous cases
        case byte other: 
            break;
    }
}

end example