is / as / Pattern Matching
| Since: | is / as | C# 1.0(2002) |
|---|---|---|
| Pattern Matching | C# 7.0(2017) |
The is operator checks an object's type, as safely casts it, and pattern matching (switch expressions and when guards) was enhanced in C# 8 and later.
Syntax
bool result = obj is Type;
// Checks the type and assigns to a variable (pattern variable).
if (obj is Type variable) { ... }
// Safe cast (returns null on failure instead of throwing).
Type variable = obj as Type;
// Pattern matching with a switch expression.
string result = variable switch {
Type1 v => ...,
Type2 v when condition => ...,
_ => defaultValue
};
Syntax List
| Syntax | Description |
|---|---|
| obj is Type | Returns bool indicating whether obj is the specified type. Returns false for null. |
| obj is Type v | Assigns to variable v if the type matches (type pattern variable). Available from C# 7. |
| obj as Type | Attempts type conversion. Returns null on failure instead of throwing an exception. Cannot be used with value types. |
| switch expression | C# 8 and later. Each case is written with an arrow (=>) and the whole expression returns a value. |
| when guard | Adds an additional condition to a switch case. |
Sample Code
Program.cs
using System;
// Type check with is.
object value = "Hello";
if (value is string text)
{
Console.WriteLine($"String: {text.ToUpper()}"); // HELLO
}
// Safe cast with as.
object number = 42;
string str = number as string; // Does not crash on failure.
Console.WriteLine(str == null ? "Conversion failed" : str); // Conversion failed
// Pattern matching with switch expression.
object[] testData = { 42, "abc", 3.14, true, null };
foreach (object item in testData)
{
string description = item switch
{
int n when n > 100 => $"Large integer: {n}",
int n => $"Integer: {n}",
string s => $"String: {s}",
double d => $"Double: {d}",
bool b => $"Boolean: {b}",
null => "null",
_ => "Unknown type"
};
Console.WriteLine(description);
}
This produces the following output:
dotnet script is_as_pattern_match.csx String: HELLO Conversion failed Integer: 42 String: abc Double: 3.14 Boolean: True null
Practical Pattern: Class Hierarchy Dispatch
A pattern for processing derived classes stored in a base-class collection by dispatching on type with pattern matching.
PatternDispatch.cs
using System;
using System.Collections.Generic;
// Base class for members.
abstract class Member
{
public string Name { get; init; }
}
class Worker : Member
{
public int Score { get; init; }
}
class Client : Member
{
public bool HasFlag { get; init; }
}
class External : Member
{
public int RemainingQuota { get; init; }
}
List<Member> members = new()
{
new Worker { Name = "user_1", Score = 210 },
new Client { Name = "user_2", HasFlag = true },
new External { Name = "user_3", RemainingQuota = 99999 },
new Worker { Name = "user_4", Score = 190 },
new Client { Name = "user_5", HasFlag = true },
};
// Dispatches on type with pattern matching.
foreach (Member m in members)
{
string info = m switch
{
Worker w when w.Score >= 200 => $"[Senior Worker] {w.Name} (Score: {w.Score})",
Worker w => $"[Worker] {w.Name} (Score: {w.Score})",
Client cl when cl.HasFlag => $"[Client/Flagged] {cl.Name}",
Client cl => $"[Client] {cl.Name}",
External ex => $"[External] {ex.Name} (Quota: {ex.RemainingQuota})",
_ => $"[Unknown] {m.Name}"
};
Console.WriteLine(info);
}
This produces the following output:
dotnet script pattern_dispatch.csx [Senior Worker] user_1 (Score: 210) [Client/Flagged] user_2 [External] user_3 (Quota: 99999) [Worker] user_4 (Score: 190) [Client/Flagged] user_5
Practical Pattern: Property Pattern
Property patterns (C# 8 and later) allow matching based on an object's property values.
PropertyPattern.cs
using System;
record Record(string Owner, bool Active, int PageCount);
static string DescribeRecord(Record record) => record switch
{
{ Owner: "user_1", Active: true } => "Main record: in use (owner: user_1)",
{ Owner: "user_2", PageCount: > 0 } => $"Reference record: {record.PageCount} pages",
{ Active: false } => $"{record.Owner}'s record: completed",
_ => $"{record.Owner}'s record"
};
var records = new[]
{
new Record("user_1", true, 50),
new Record("user_2", true, 200),
new Record("user_3", false, 0),
new Record("user_4", true, 30),
};
foreach (var record in records)
Console.WriteLine(DescribeRecord(record));
This produces the following output:
dotnet script property_pattern.csx Main record: in use (owner: user_1) Reference record: 200 pages user_3's record: completed user_4's record
Common Mistakes
Common Mistake 1: No Null Check After as
When an as cast fails, it returns null. Accessing members of the result without a null check causes a NullReferenceException.
using System; object obj = 42; // NG: str will be null if conversion fails. string str = obj as string; Console.WriteLine(str.Length); // NullReferenceException is thrown.
This produces the following output:
dotnet run Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
using System;
object obj = 42;
// OK: The is pattern variable only enters the block when the cast succeeds.
if (obj is string str)
{
Console.WriteLine(str.Length);
}
else
{
Console.WriteLine("Not a string.");
}
This produces the following output:
dotnet run Not a string.
Common Mistake 2: Using as with Value Types
as can only be used with reference types. Using it with value types such as int or struct causes a compile error.
using System;
object obj = 42;
// NG: int is a value type; as cannot be used (compile error).
// int n = obj as int; // error CS0077: The as operator must be used with a reference type
// OK: Use the is pattern.
if (obj is int n)
Console.WriteLine($"int: {n}");
// OK: Use an explicit cast (throws InvalidCastException on failure).
int m = (int)obj;
Console.WriteLine($"int: {m}");
This produces the following output:
dotnet run int: 42 int: 42
Overview
Since C# 7, is can check the type and assign the variable at the same time, eliminating the need to separately cast after an is check. You can write it in one line: if (obj is string s).
as works for reference type casts but cannot be used with value types (int, struct, etc.). Using it without checking for null afterward leads to a NullReferenceException. Always add a null check.
For providing default values when a value is null, see null coalescing operator ?? / ??=.
If you find any errors or copyright issues, please contact us.