C#笔记——模式匹配与运算符

本文最后更新于:2023年11月12日 晚上

模式匹配

看到模式匹配就想起被正则表达式regex折磨(悲

C#中,我们可以使用is表达式、switch语句、switch表达式来将输入表达式与任意数量的特征匹配,最简单的例如:if (sth is TargetClass)

就我个人而言,结合在一起的话感觉有点像是switch语法糖。

C#提供了许多模式匹配,有:声明、类型、常量、关系、逻辑、属性、位置、var、弃元和列表模式等,这里仅对部分模式做出说明。

声明和类型模式

使用声明和类型模式检查表达式的运行时类型是否与给定类型兼容。借助声明模式,还可以声明新的局部变量。例如:

1
2
3
4
5
6
7
8
9
int a = 20;
if (a is int b)
{
Console.WriteLine($"a is {b}."); // output: a is 20.
}
if (a is not null)
{
Console.WriteLine($"a is not null."); // output: a is not null.
}

C#9.0开始,可对此使用类型模式,如以下示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class Vehicle {}
public class Car : Vehicle {}
public class Truck : Vehicle {}

public static class TollCalculator
{
public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{
Car => 2.00m, // or Car _ => 2.00m,
Truck => 7.50m, // or Truck _ => 7.50m,
null => throw new ArgumentNullException(nameof(vehicle)),
_ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
};
}

常量模式

可使用常量模式来测试表达式结果是否等于指定的常量,根据匹配的值返回对应的值:

1
2
3
4
5
6
7
8
9
public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
1 => 12.0m,
2 => 20.0m,
3 => 27.0m,
4 => 32.0m,
0 => 0.0m,
_ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),
};

逻辑和关系模式

C#9.0开始,可使用关系模式将表达式结果与常量进行比较,当然,你也可以往其中加入逻辑运算符andornot来组成更为复杂的模式匹配:

1
2
3
4
5
6
7
8
9
10
11
12
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14)));  // output: spring
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19))); // output: summer
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17))); // output: winter

static string GetCalendarSeason(DateTime date) => date.Month switch
{
>= 3 and < 6 => "spring",
>= 6 and < 9 => "summer",
>= 9 and < 12 => "autumn",
12 or (>= 1 and < 3) => "winter",
_ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
};

属性模式

可以使用属性模式将表达式的属性或字段与嵌套模式进行匹配:

1
static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };

还可以将运行时类型检查和变量声明添加到属性模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Console.WriteLine(TakeFive("Hello, world!"));  // output: Hello
Console.WriteLine(TakeFive("Hi!")); // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' })); // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' })); // output: abc

static string TakeFive(object input) => input switch
{
string { Length: >= 5 } s => s.Substring(0, 5),
string s => s,

ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
ICollection<char> symbols => new string(symbols.ToArray()),

null => throw new ArgumentNullException(nameof(input)),
_ => throw new ArgumentException("Not supported input type."),
};

位置模式

我将其称之为元组模式匹配,因为它确实跟元组有关,使用元组表达式来将多个输入与各种模式进行匹配:

1
2
3
4
5
6
7
8
9
10
11
static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate)
=> (groupSize, visitDate.DayOfWeek) switch
{
(<= 0, _) => throw new ArgumentException("Group size must be positive."),
(_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
(>= 5 and < 10, DayOfWeek.Monday) => 20.0m,
(>= 10, DayOfWeek.Monday) => 30.0m,
(>= 5 and < 10, _) => 12.0m,
(>= 10, _) => 15.0m,
_ => 0.0m,
};

列表模式

C#11开始,可以将数组或列表与模式的序列进行匹配:

1
2
3
4
5
6
int[] numbers = { 1, 2, 3 };

Console.WriteLine(numbers is [1, 2, 3]); // True
Console.WriteLine(numbers is [1, 2, 4]); // False
Console.WriteLine(numbers is [1, 2, 3, 4]); // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]); // True

如果需要仅匹配开头或结尾的几个元素,可以使用切片模式..

1
2
3
4
5
Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]);  // True

Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]); // True

Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]); // True

还可以在切片模式中嵌套子模式(比如嵌套逻辑模式什么的)。

运算符

其实这里主要记录一下对我来说比较陌生的运算符(或者说之前没学过的),所以会不全,要全请前往官方文档捏~

!null包容)运算符

一元后缀!运算符是null包容运算符或null抑制运算符。在已启用的可为空的注释上下文中,使用null包容运算符来取消上述表达式的所有可为null警告。

????=运算符——Null合并操作符

先从??运算符(null合并运算符)说起。如果左操作数的值不为null,则返回该值;否则,它会计算右操作数并返回其结果。如果左操作数的计算结果为非null,则??运算符不会计算其右操作数。

1
2
int? a = null;
Console.WriteLine((a ?? 3)); // output: 3

然后是??=运算符(null合并赋值运算符)。仅当左操作数的计算结果为null时,??=才会将其右操作数的值赋值给其左操作数。如果左操作数的计算结果为非null,则??=不会计算其右操作数。

1
2
3
4
int? a = null;
Console.WriteLine((a is null)); // output: True
a ??= 3;
Console.WriteLine(a); // output: 3

Null条件运算符?.?[]

仅当操作数的计算结果为非null时,null条件运算符才对其操作数应用成员访问(?.)或元素访问(?[])操作;否则,它会返回null。即:

  • 如果a的计算结果为null,则a?.xa?[x]的结果为null
  • 如果a的计算结果为非null,则a?.xa?[x]的结果将分别与a.xa[x]的结果相同。

::运算符 - 命名空间别名运算符

使用命名空间别名限定符::访问已设置别名的命名空间的的成员,比如:

1
2
3
4
5
6
7
using forwinforms = System.Drawing;
using forwpf = System.Windows;

public class Converters
{
public static forwpf::Point Convert(forwinforms::Point point) => new forwpf::Point(point.X, point.Y);
}

这里有一只爱丽丝

希望本文章能够帮到您~


C#笔记——模式匹配与运算符
https://map1e-g.github.io/2023/09/07/CSharp-learning-5/
作者
MaP1e-G
发布于
2023年9月7日
许可协议