Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Declaration, constant, and
Tip
This article is part of the Fundamentals section for developers who already know at least one programming language and are learning C#. Start with the pattern matching overview if you haven't used C# patterns before. For complete language rules, see the patterns reference.
A pattern is applied to an input expression. C# evaluates the expression, then the pattern tests or captures the resulting value. Declaration, constant, and var patterns answer three practical questions:
- Declaration pattern: Did the expression produce a non-null value of a compatible run-time type? If so, declare a variable for that value.
- Constant pattern: Did the expression produce one specific constant value?
varpattern: What value did the expression produce? Capture it without first testing its type or value.
Test and capture a type with a declaration pattern
A declaration pattern consists of a type and a designation. The type specifies what run-time type to test. The designation declares the variable that receives the matching value.
The following example receives an object, so the expression might produce many different types. The declaration pattern lets the matching branch use a decimal amount without a separate type test and cast:
static void PrintPrice(object value)
{
if (value is decimal amount)
{
Console.WriteLine($"Price: {amount:C}");
}
}
In value is decimal amount:
valueis the input expression. C# evaluates it first.decimalis the tested type. The pattern matches when the evaluated value is non-null and its run-time type is compatible withdecimal.amountis the designation. When the pattern matches, it declaresamountand assigns the decimal value to it.
The compiler tracks whether a local variable receives a value before your code reads it. This tracking is called definite assignment. Inside the if block, the compiler knows that amount was assigned because the block runs only when the pattern matches. The compiler produces an error if your code tries to access amount outside the if block. If value isn't a decimal value, the variable amount isn't assigned to a value.
Choose a declaration pattern when the matching branch needs to use the result as the tested type. It combines the test, conversion, and variable declaration, which avoids repeating the expression or writing a separate cast.
You can also use declaration patterns when one expression might produce several useful types:
static string FormatSensorValue(object reading) =>
reading switch
{
int count => $"Count: {count}",
double temperature => $"Temperature: {temperature:F1}°C",
string message => $"Message: {message}",
_ => "Unsupported reading"
};
Each arm declares a variable of the matched type because the result needs that type's formatting behavior. A declaration pattern matches only when the evaluated value is non-null and already has a run-time type that's compatible with the tested type through the conversions permitted for patterns. null has no run-time type for the pattern to match. The pattern also doesn't run user-defined conversion operators: It's a type test and capture, not a request to convert the value to another type. For the complete compatibility rules, see Declaration and type patterns.
Match a specific value with a constant pattern
A constant pattern tests whether an expression produces a particular constant, such as a number, string, Boolean, enum member, declared const value, or null.
Constant patterns fit a switch expression when several known values each produce a different result:
static string GetCommandMessage(Command command) =>
command switch
{
Command.Start => "Starting",
Command.Stop => "Stopping",
Command.Pause => "Pausing",
_ => "Unknown command"
};
Command is an enum, a type that defines a set of named constants. Command.Start, Command.Stop, and Command.Pause are its enum members, so each switch arm uses a constant pattern to test one named command value.
Choose this form when the command can have several discrete meanings. The switch arms keep the values and their results together. For one simple equality comparison, an if statement such as if (command == Command.Start) is usually easier to read.
Constant-pattern matching uses built-in language equality rules rather than a user-defined == operator. For the detailed equality and conversion rules, see the constant pattern reference.
The null constant pattern is useful for a reliable null check:
static bool HasText(string? text) => text is not null;
Choose is null or is not null when you're checking null state. These patterns don't call a user-defined equality operator, even when the expression's type overloads ==.
Capture a result for a guard with a var pattern
A var pattern matches every result, including null, and declares a variable whose type is the input expression's compile-time type. It can capture a computed value while another pattern is already matching an object:
static string GetDeliveryMessage(object delivery) =>
delivery switch
{
ExpressDelivery express
when EstimateDays(express) is var days && days <= 2
=> $"Arrives in {days} day{(days == 1 ? "" : "s")}",
ExpressDelivery => "Express delivery for your location takes more than two days",
_ => "Standard delivery"
};
static int EstimateDays(ExpressDelivery delivery) =>
delivery.MilesAway <= 500 ? 1 :
delivery.MilesAway <= 1_000 ? 2 : 3;
record ExpressDelivery(int MilesAway);
The declaration pattern ExpressDelivery express first captures the delivery object as express. The method call EstimateDays(express) is the input expression for the var pattern. C# evaluates that method call, and var days captures the resulting estimate as days without testing its type or value. The estimate can be one or two days when the guard succeeds. The arm result needs the captured value to report the actual number of days.
An ordinary local variable can't be declared between a switch-arm pattern and its when guard. Calling EstimateDays(express) again in the result would repeat the calculation. Choose this var pattern form when the code is already matching, and both the guard and result need a computed intermediate value.
If you don't need the captured value, use the discard pattern _ instead of declaring a variable.