Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

C# Dictionary

  1. Home
  2. C# Dictionary
  3. Pattern Matching (C#)

Pattern Matching (C#)

Pattern matching in C# lets you check a value's type, structure, and content all at once, and bind the result to a variable. This page covers the is keyword with type patterns, constant patterns, and property patterns; patterns in switch statements and switch expressions; list patterns (C# 11 and later); and guard conditions with the when clause.

Syntax

if (object is Type variableName) { ... }

if (value is constant) { ... }

if (object is { PropertyName: value }) { ... }

// Pattern in a switch statement (C# 7 and later): supports type patterns and when guards.
switch (variable) {
	case Type v when condition:
		// body
		break;
}

// Pattern in a switch expression (C# 8 and later): each arm is written with an arrow (=>).
var result = variable switch {
	Type v when condition => returnValue,
	{ PropertyName: value } => returnValue,
	constant => returnValue,
	_ => defaultValue
};

// List pattern (C# 11 and later): matches elements of an array or list by pattern.
if (array is [firstElement, .., lastElement]) { ... }

Pattern types

Pattern nameExample syntaxDescription
Type patternobj is Worker sChecks whether the object is of the specified type and, if so, assigns it to variable s. Available from C# 7.
Constant patternobj is null / obj is 42Checks whether the value equals a specific constant.
Property patterns is { Level: "S" }Checks whether a property of the object matches the specified value. Available from C# 8.
Relational patternn is >= 1000Expresses a numeric comparison as a pattern. Available from C# 9.
Logical patternn is > 0 and < 100Combines patterns using and, or, and not. Available from C# 9.
List patternarr is [first, .., last]Matches elements, length, first element, last element, and more of an array or list. Available from C# 11.
when guardcase Type v when condition:Adds an extra condition to a switch case or arm. Available from C# 7.

Sample code

TypePattern.cs
using System;

// Base class for workers.
class Worker {
	public string Name { get; set; }
	public string Level { get; set; }
	public Worker(string name, string level) {
		Name = name;
		Level = level;
	}
}

// Subclass for engineers: holds a score value.
class Engineer : Worker {
	public int Score { get; set; }
	public Engineer(string name, string level, int score)
		: base(name, level) {
		Score = score;
	}
}

// Subclass for managers: holds a power value.
class Manager : Worker {
	public int Power { get; set; }
	public Manager(string name, string level, int power)
		: base(name, level) {
		Power = power;
	}
}

class TypePattern {
	static void Main() {

		// Use is to check the type of an object variable and assign it at the same time.
		object entity = new Engineer("member_1", "S", 999999);

		// The is expression checks the type and assigns the result to s simultaneously.
		if (entity is Engineer s) {
			Console.WriteLine(s.Name + " score: " + s.Score);
			// → member_1 score: 999999
		}

		// You can use is to check for null.
		object unknown = null;

		if (unknown is null) {
			Console.WriteLine("Entity is null."); // This branch runs.
		}

		// Write the property name and expected value inside { }.
		var worker2 = new Engineer("member_2", "A", 80000);

		if (worker2 is { Level: "A" }) {
			Console.WriteLine(worker2.Name + " is a Level A member."); // This branch runs.
		}

		// You can also combine a property pattern with a type pattern.
		// This matches only a Level S engineer with Score >= 900000.
		if (entity is Engineer { Level: "S", Score: >= 900000 } topWorker) {
			Console.WriteLine(topWorker.Name + " is the top-rated member."); // This branch runs.
		}

		// You can express numeric comparisons directly as patterns.
		int rank = 85000;

		if (rank is >= 80000 and < 1000000) {
			Console.WriteLine("Score " + rank + " is near-S class."); // This branch runs.
		}
	}
}

This produces the following output:

dotnet script TypePattern.cs
member_1 score: 999999
Entity is null.
member_2 is a Level A member.
member_1 is the top-rated member.
Score 85000 is near-S class.
SwitchPattern.cs
using System;

// Base class for members.
class Member {
	public string Name { get; set; }
	public string Level { get; set; }
	public Member(string name, string level) {
		Name = name;
		Level = level;
	}
}

// Engineer class.
class Engineer : Member {
	public int Score { get; set; }
	public string SkillType { get; set; }
	public Engineer(string name, string level, int score, string skill)
		: base(name, level) {
		Score = score;
		SkillType = skill;
	}
}

// Task worker class.
class TaskWorker : Member {
	public int Strength { get; set; }
	public TaskWorker(string name, string level, int strength)
		: base(name, level) {
		Strength = strength;
	}
}

class SwitchPattern {
	static void Main() {

		// Combine type patterns and when guards in case labels to dispatch by type and condition.
		object[] entities = {
			new Engineer("member_1", "S", 999999, "skill_alpha"),
			new Engineer("member_2", "A", 80000, "skill_beta"),
			new TaskWorker("item_a", "S", 200000),
			new TaskWorker("item_b", "S", 160000),
			42
		};

		foreach (object entity in entities) {
			switch (entity) {
				// Level S engineer with score above 900000.
				case Engineer j when j.Level == "S" && j.Score > 900000:
					Console.WriteLine("[Top] " + j.Name + " (" + j.SkillType + ")");
					break;
				// All other engineers.
				case Engineer j:
					Console.WriteLine("[Engineer] " + j.Name + " / " + j.Level + " / Score: " + j.Score);
					break;
				// Level S task workers.
				case TaskWorker c when c.Level == "S":
					Console.WriteLine("[S-Task] " + c.Name + " / Strength: " + c.Strength);
					break;
				// Any other type.
				default:
					Console.WriteLine("[Unknown] " + entity);
					break;
			}
		}

		Console.WriteLine();

		foreach (object entity in entities) {
			string label = entity switch {
				// Combine a property pattern with a when guard.
				Engineer { Level: "S" } j when j.Score > 900000
					=> "[Top-Rank] " + j.Name,
				Engineer j
					=> "[Engineer] " + j.Name + " (" + j.Level + ")",
				TaskWorker { Level: "S" } c
					=> "[S-Task] " + c.Name,
				TaskWorker c
					=> "[Task] " + c.Name + " (" + c.Level + ")",
				// Null pattern.
				null
					=> "(null)",
				// Wildcard: default for anything that does not match the arms above.
				_
					=> "[Other] " + entity
			};
			Console.WriteLine(label);
		}
	}
}

This produces the following output:

dotnet script SwitchPattern.cs
[Top] member_1 (skill_alpha)
[Engineer] member_2 / A / Score: 80000
[S-Task] item_a / Strength: 200000
[S-Task] item_b / Strength: 160000
[Unknown] 42

[Top-Rank] member_1
[Engineer] member_2 (A)
[S-Task] item_a
[S-Task] item_b
[Other] 42
ListPattern.cs
using System;

class ListPattern {
	static void Main() {

		// Match the structure of an array or list by element count, first element, last element, etc.
		// The slice pattern (..) matches any number of elements (including zero).

		// Match team assignments stored as string arrays.
		string[][] teams = {
			new[] { "member_2" },
			new[] { "member_2", "member_3" },
			new[] { "member_2", "member_3", "member_4" },
			new[] { "member_1", "member_5", "member_6", "member_7" },
			new string[0] // empty array
		};

		foreach (string[] team in teams) {
			string result = team switch {
				// Matches an empty array.
				[] =>
					"Team has 0 members.",
				// Matches a team of exactly one person.
				[var only] =>
					"Solo task: " + only,
				// Matches a two-person team led by member_2.
				["member_2", var partner] =>
					"member_2 team (2): member_2 & " + partner,
				// Matches any team of 3 or more led by member_2.
				["member_2", ..] =>
					"member_2 team (3+): " + string.Join(" / ", team),
				// Captures the first element as first and the last as last.
				[var first, .., var last] =>
					"Leader: " + first + " / Tail: " + last + " (" + team.Length + " total)",
				// Fallback (not actually reached because all cases are covered).
				_ =>
					"No match"
			};
			Console.WriteLine(result);
		}

		Console.WriteLine();

		// Match score history logs (int arrays).
		int[][] scoreLogs = {
			new[] { 80000, 85000, 90000 }, // rising trend
			new[] { 100000, 95000, 88000 }, // falling trend
			new[] { 75000 }, // single record
			new int[0] // no records
		};

		foreach (int[] log in scoreLogs) {
			string trend = log switch {
				// No records.
				[] =>
					"No records",
				// Exactly one record.
				[var only] =>
					"Record: " + only,
				// Last value greater than first: rising trend.
				[var first, .., var last] when last > first =>
					"Rising: " + first + " → " + last,
				// Last value less than first: falling trend.
				[var first, .., var last] when last < first =>
					"Falling: " + first + " → " + last,
				// First and last are equal: flat.
				_ =>
					"Flat"
			};
			Console.WriteLine(trend);
		}
	}
}

This produces the following output:

dotnet script ListPattern.cs
Solo task: member_2
member_2 team (2): member_2 & member_3
member_2 team (3+): member_2 / member_3 / member_4
Leader: member_1 / Tail: member_7 (4 total)
Team has 0 members.

Rising: 80000 → 90000
Falling: 100000 → 88000
Record: 75000
No records

Common Mistakes

Using a type-pattern variable outside the if block

The variable s in if (entity is Engineer s) is only assigned when the type matches. Outside the if block, s may not have a value, causing the compiler to report a warning or error. Always use type-pattern variables inside the if block.

object entity = "string data";

if (entity is int n) {
	Console.WriteLine(n * 2);
}
// Using n here causes a compile error.
// Console.WriteLine(n); // Error: n is not definitely assigned.

Forgetting the wildcard (_) in a switch expression

A switch expression must cover all possible cases. Without the wildcard pattern _, passing a value that matches none of the arms causes a runtime exception (SwitchExpressionException).

object value = 3.14;

// Without _, a value that is neither int nor string throws an exception.
// string result = value switch {
//     int n => "integer",
//     string s => "string",
// };

string result = value switch {
	int n => "integer",
	string s => "string",
	_ => "other"
};

Overview

Pattern matching in C# lets you test multiple conditions — type, property values, numeric ranges, and more — in a single expression. It eliminates the need to write separate type casts and null checks, keeping your code concise.

Type patterns (is Type variableName) are the most fundamental form. Writing if (entity is Engineer s) performs the type check and variable assignment in one line. If the type does not match, the variable s is not assigned, so do not use it outside the if block.

Property patterns ({ PropertyName: value }) let you check an object's internal state directly in the pattern. Combined with a type pattern — is Engineer { Level: "S" } s — you can perform both a type check and a property check simultaneously.

List patterns (C# 11 and later) match the element structure of an array or list. The slice pattern (..) lets you skip any number of elements while extracting only the first and last, making such operations concise.

when guards add extra conditions to a pattern. Even if the type matches, execution moves to the next arm (case) if the when condition is not satisfied. Spreading complex conditions across multiple arms keeps long if / else if chains organized.

For details on how to choose between switch statements and switch expressions, see switch statement / switch expression. For safe type casting, also see is / as / pattern matching.

If you find any errors or copyright issues, please .