Pattern matching in C# is a powerful feature that allows you to inspect and deconstruct values in a declarative way. This tutorial covers the basics, advanced concepts, and best practices for using pattern matching in C#.
Pattern matching allows you to check a value against a pattern. It is commonly used with switch statements and expressions, but can also be used in other contexts.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
object obj = 5;
if (obj is int i)
{
Console.WriteLine($"Integer: {i}");
}
}
}
Type patterns allow you to check the type of an object and cast it to that type if the pattern matches.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
object obj = "Hello, World!";
if (obj is string s)
{
Console.WriteLine($"String: {s}");
}
}
}
Constant patterns allow you to check if a value is equal to a constant.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
int number = 5;
if (number is 5)
{
Console.WriteLine("Number is 5");
}
}
}
The var pattern matches any value and binds it to a new variable.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
object obj = 42;
if (obj is var x)
{
Console.WriteLine($"Value: {x}");
}
}
}
Discard patterns match any value but do not bind it to a variable. They are useful when you need to ignore certain values.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
object obj = 100;
if (obj is int _)
{
Console.WriteLine("Object is an integer");
}
}
}
Property patterns allow you to match on the properties of an object. This is useful for deconstructing objects and checking their properties.
using System;
namespace PatternMatchingExamples;
public class Program
{
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public static void Main(string[] args)
{
var person = new Person { Name = "Alice", Age = 30 };
if (person is { Name: "Alice", Age: 30 })
{
Console.WriteLine("Matched Alice, 30 years old");
}
}
}
Positional patterns allow you to deconstruct an object using its deconstruction method or positional properties.
using System;
namespace PatternMatchingExamples;
public class Program
{
public class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
}
public static void Main(string[] args)
{
var point = new Point(1, 2);
if (point is (1, 2))
{
Console.WriteLine("Point is (1, 2)");
}
}
}
Tuple patterns allow you to match tuples and deconstruct their values.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
var tuple = (1, "Hello");
if (tuple is (1, "Hello"))
{
Console.WriteLine("Tuple matched (1, \"Hello\")");
}
}
}
Relational patterns allow you to compare a value to another value using relational operators.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
int number = 42;
if (number is > 40)
{
Console.WriteLine("Number is greater than 40");
}
}
}
Logical patterns allow you to combine patterns using logical operators such as and, or, and not.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
int number = 42;
if (number is > 30 and < 50)
{
Console.WriteLine("Number is between 30 and 50");
}
}
}
Pattern matching can be used in switch statements to create more readable and concise code.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
object obj = 42;
switch (obj)
{
case int i:
Console.WriteLine($"Integer: {i}");
break;
case string s:
Console.WriteLine($"String: {s}");
break;
default:
Console.WriteLine("Unknown type");
break;
}
}
}
Switch expressions provide a more concise syntax for pattern matching compared to switch statements.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
object obj = 42;
var result = obj switch
{
int i => $"Integer: {i}",
string s => $"String: {s}",
_ => "Unknown type"
};
Console.WriteLine(result);
}
}
You can use pattern matching with lists to match on the structure and contents of lists.
using System;
using System.Collections.Generic;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
var list = new List<int> { 1, 2, 3 };
if (list is [1, 2, 3])
{
Console.WriteLine("List matched [1, 2, 3]");
}
}
}
Advanced pattern matching techniques include combining multiple patterns, using custom patterns, and optimizing pattern matching performance.
using System;
namespace PatternMatchingExamples;
public class Program
{
public static void Main(string[] args)
{
object obj = new Person { Name = "Alice", Age = 30 };
switch (obj)
{
case Person { Name: "Alice", Age: > 20 }:
Console.WriteLine("Matched Alice, older than 20");
break;
case Person { Name: "Bob", Age: < 20 }:
Console.WriteLine("Matched Bob, younger than 20");
break;
default:
Console.WriteLine("Unknown person");
break;
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
Pattern matching in C# is a versatile feature that allows you to write more expressive and concise code. By understanding and using pattern matching effectively, you can simplify your code and make it more readable.