C# patterns
2023-02-09 C# Pattern is switch when Deconstruct recordIn C# 7 was introduced interesting operator is
that allows to match (and also unpack) various types. In subsequent versions of the language its ability was greatly improved with many other patterns.
Basic idea is like this
if (obj is string)
Console.WriteLine("obj is string");
It returns true if the supplied item is of specified type. That can be useful if we get an arbitrary object and we would like to detect if it has capabilities we need. At the same time, it can also provide cast to specified type. This is called declaration and type pattern.
if (input is string s)
Console.WriteLine(s.ToLower());
Later there were introduced constant, relational, positional and property patterns. Also there is new switch
expression that allows to pattern match against number of rules with nice checking that all options are properly exhausted
string DescribeStringLength(string str) =>
str switch
{
null => "Null string",
"Hello" => "Hello string",
{ Length: 0 } => "Empty string",
{ Length: 1 } => "Character",
{ Length: > 1 } => "Longer string",
};
The example above shows checking against null, constant string and number of options in the Length
property.
Here is examples of the syntax
C# | Meaning |
---|---|
_ |
Matches anything |
List<int> |
The item has type List<int> |
List<int> list |
The item has type List<int> and is extracted into list variable |
10 |
The item matches constant 10 |
< -3.0 |
The item is smaller than -3.0 |
>= 3 and < 6 |
The item is within the interval |
[1, 2, 3] |
The extracted items are 1 , 2 , and 3 |
Point { X: 10 } |
The item is of type Point with property X equals 10 |
{} |
The item is non-null |
("12345", _, _) |
First extracted item of three is string "12345" |
The deconstruction of an item is by default available in Tuple
s, Array
s, or record
s but it is possible to make any class so with method Deconstruct
class City
{
public string Name { get; }
public int Area { get; }
public double Population { get; }
public City(string name, int area, double population) =>
(Name, Area, Population) = (name, area, population);
public void Deconstruct(out string name, out int area, out double population) =>
(name, area, population) = (Name, Area, Population);
}
This is just an example, it is easier to build record class like this
record class City(string Name, int Area, double Population);
Then it is possible to match it like in this a little contrived example that uses when
guard to the pattern
City newYork = new City("New York", Population: 8804190, Area: 1223);
Console.WriteLine($"{newYork.Name} population density: " + newYork switch
{
var (name, area, population) when population > 100000 => "High",
var (name, area, population) when population < 50000 => "Medium",
var (name, area, population) when population < 25000 => "Low",
_ => "Unknown"
});