Csharp 中的模式匹配示例

我第一次接触模式匹配是在几年前,当时我正在摆弄一些函数式编程语言,比如 Elixir 和 F#。当我用 C#(以及 JavaScript)编写代码时,我缺少对值或对象进行模式匹配的能力。

我很高兴看到 C# 7 引入了 模式匹配的第一个版本,它支持最低限度的功能。由于我之前接触过模式匹配,所以这有点令人失望,因为它没有我习惯的所有可能性。幸运的是,C# 8 的发布通过支持更多功能扩展了模式匹配语法,这些功能在 C# 9 中得到了进一步增强,而且这种趋势似乎在即将推出的 C# 版本中仍在继续。

为什么要进行模式匹配

那么我为什么对此感到兴奋呢?

使用模式匹配可以做的所有事情,在不使用模式匹配的情况下也可以做到,但看起来不太好。
模式匹配可以将复杂的 if-else 或 switch 语句变成紧凑的代码块。
因此,随着模式匹配的增加,我的开发体验得到了改善。
它的优势在于它具有表现力,使其更易于阅读,更难编写错误。
额外的好处是编译器充当安全网(稍后会详细介绍),它会警告未处理的情况并防止情况相互冲突。
这不是普通 if-else 语句所能提供的功能,不幸的是,它过去曾困扰过我。
很高兴知道编译器会报告愚蠢的错误来支持你。
对于本文的其余部分,让我们看一些如何使用模式匹配和 switch 表达式的示例。

一个简单的例子

最简单的例子是检查简单的真/假值。
模式匹配将输入值(下例中为 false)与一组定义的模式进行比较,将它们视为一组条件。 switch 表达式中的模式从上到下进行评估,并执行导致匹配的第一个案例。

var output = false switch
{
    true => "true",
    false => "false",
};
// output: false

常量模式

我们可以使用模式匹配来检查变量是否具有常量值。
可匹配的类型是字符串、字符、数字和枚举。

var output = 4 switch
{
    1 => "one",
    2 => "two",
    3 => "three",
    4 => "four",
    5 => "five",
};
// output: four

丢弃模式

由于上例中的 switch 表达式中并非包含所有数字,因此 C# 编译器会警告存在未涵盖的情况。

warning CS8509: The switch expression does not handle all possible values
of its input type (it is not exhaustive).
For example, the pattern '0' is not covered.

为了解决此警告,我们可以添加一个后备案例。
将其视为 switch 语句中的默认案例,即当先前的模式均不匹配时调用的通配符。在下面的示例中,丢弃运算符(表示为下划线“_”)用于匹配所有其他可能的值。

var output = 9 switch
{
    1 => "one",
    2 => "two",
    3 => "three",
    4 => "four",
    5 => "five",
    _ => "other"
};
// output: other

变量模式

匹配的值可以分配给变量

在下面的例子中,变量充当与任何值匹配的通配符。

类型模式 的输入值与类型匹配时,也可以定义变量,在这种情况下,创建的变量是类型化的。然后可以在执行表达式(=> 之后)中使用该变量来创建返回值。

此外,可以将**when 语句添加到模式中,为模式添加额外的保护**。
when 语句可用于将非常量值与条件匹配,例如,在匹配的变量上调用方法。除了匹配正在匹配的对象之外,还可以在 when 语句中包含其他对象。

var greetWithName = true;
var output = "Mrs. Kim" switch
{
    _ when greetWithName == false => $"Hi",
    "Tim" => "Hi Tim!",
    var str when str.StartsWith("Mrs.") || str.StartsWith("Mr.") => $"Greetings {str}",
    var str => $"Hello {str}",
};
// output: Greetings Mrs. Kim

关系模式

我们使用关系运算符=><>=<=)来测试输入是否等于、大于或小于另一个值。

var output = 4 switch
{
    < 3 => "less than 3",
    <= 7 => "less than or equal to 7",
    < 1 => "less than 10",
    _ => "greater than or equal to 10"
};
// output: ?

你发现示例中的错误了吗?
如果没有,你不必担心,因为这次编译器会抛出一个错误来解决我的错误。

error CS8510: The pattern is unreachable. It has already been handled by
a previous arm of the switch expression or it is impossible to match.

让我们通过修正模式来修复这个错误。

var output = 4 switch
{
    < 3 => "less than 3",
    <= 7 => "less than or equal to 7",
    < 10 => "less than 10",
    _ => "greater than or equal to 10"
};
// output: less than or equal to 7

多种模式

为了组合模式或否定值,我们使用逻辑运算符andornot)。

var output = 4 switch
{
    1 or 2 or 3 => "1, 2, or 3",
    > 3 and <= 6 => "between 3 and 6",
    not 7 => "not 7",
    _ => "7"
};
// output: between 3 and 6

元组模式

匹配单个值很好,但在很多情况下用处不大。
要匹配多个值,我们可以使用元组对多个输入值进行模式匹配

var output = (5, false) switch
{
    (< 4, true) => "lower than 4 and true",
    (< 4, false) => "lower than 4 and false",
    (4, true) => "4 and true",
    (5, _) => "five and something",
    (_, false) => "something and false",
    _ => "something else",
};
// output: five and something

属性模式

当输入是对象时,我们可以向对象的属性添加模式。

var output = new User("Tim Deschryver", "Developer") switch
{
    { Role: "Admin" } => "the user is an admin",
    { Role: "Administrator" } => "the user is an admin",
    { Name: "Tim", Role: "Developer" } => "the user is Tim and he is a developer",
    { Name: "Tim" } => "the user is Tim and he isn't a developer",
    _ => "the user is unknown",
};
// output: the user is Tim and he is a developer

record User(string Name, string Role);

嵌套属性模式

甚至可以匹配嵌套属性

var output = new Member("Tim Deschryver", new MemberDetails(8, false)) switch
{
    { Details: { Blocked: true } } => Array.Empty<string>(),
    { Details: { MonthsSubscribed: < 3 } } => new[] { "comments" },
    { Details: { MonthsSubscribed: < 9 } } => new[] { "comments", "mention" },
    _ => new[] { "comments", "mention", "ping" },
};
// output: comments, mention

record Member(string Name, MemberDetails Details);
record MemberDetails(int MonthsSubscribed, bool Blocked);

上述示例可能难以阅读(尤其是当您需要深入了解多个对象时)。
作为补救措施,C# 10 提供了一种称为 扩展属性模式 的新语法,使其更容易阅读。
重构后的示例如下所示。好多了,对吧?

var output = new Member("Tim Deschryver", new MemberDetails(8, false)) switch
{
    { Details.Blocked: true } => Array.Empty<string>(),
    { Details.MonthsSubscribed: < 3 } => new[] { "comments" },
    { Details.MonthsSubscribed: < 9 } => new[] { "comments", "mention" },
    _ => new[] { "comments", "mention", "ping" },
};
// output: comments, mention

record Member(string Name, MemberDetails Details);
record MemberDetails(int MonthsSubscribed, bool Blocked);

类型模式

模式匹配也可用于匹配对象的类型
当您有一个充当传递的通用处理程序时,类型模式很有用。

var output = new InventoryItemRemoved(3) as object switch
{
    // the variable `added` is of type `InventoryItemAdded`
    InventoryItemAdded added => $"Added {added.Amount}",
    // the variable `removed` is of type `InventoryItemRemoved`
    InventoryItemRemoved removed => $"Removed {removed.Amount}",
    InventoryItemDeactivated => "Deactivated",
    null => throw new ArgumentNullException()
    // the variable `o` is of type `object`
    var o => throw new InvalidOperationException($"Unknown {o.GetType().Name}")
};
// output: Removed 3

示例

到目前为止我们看到的示例都很简单,它们只是展示了构建模式的不同语法。
为了释放模式匹配的真正威力,可以组合多种模式匹配策略。

// retrieve the rate of an appointment
var holidays = new DateTime[] {...};
var output = new Appointment(DayOfWeek.Friday, new DateTime(2021, 09, 10, 22, 15, 0), false) switch
{
    { SocialRate: true } => 5,
    { Day: DayOfWeek.Sunday } => 25,
    Appointment a when holidays.Contains(a.Time) => 25,
    { Day: DayOfWeek.Saturday } => 20,
    { Day: DayOfWeek.Friday, Time.Hour: > 12 } => 20,
    { Time.Hour: < 8 or >= 18 } => 15,
    _ => 10,
};
// output: 20

record Appointment(DayOfWeek Day, DateTime Time, bool SocialRate);
// a combination of the variable pattern and the tuple pattern
var output = ("", "Tim") switch
{
    var (title, name) when title.Equals("Mrs.") || title.Equals("Mr.") => $"Greetings {title} {name}",
    var (_, name) and (_, "Tim") => $"Hi {name}!",
    var (_, name) => $"Hello {name}",
};
// output: Hi Tim!
// format a string
var contactInfo = new ContactInfo("Sarah", "Peeters", "0123456789");
var output = contactInfo switch
{
    { TelephoneNumber: not null } and { TelephoneNumber: not "" } info => $"{info.FirstName} {info.LastName} ({info.TelephoneNumber})",
    _ => $"{contactInfo.FirstName} {contactInfo.LastName}"
};
// output: Sarah Peeters (0123456789)
// C# Language Highlights: Tuple Pattern Matching https://www.youtube.com/watch?v=v_xKLwTv3AI
IEnumerable<string> sequence = new[] { "foo" };
var output = sequence switch
{
    string[] { Length: 0 } => "array with no items",
    string[] { Length: 1 } => "array with a single item",
    string[] { Length: 2 } => "array with 2 items",
    string[] => $"array with more than 2items",
    IEnumerable<string> source when !source.Any() => "empty enumerable",
    IEnumerable<string> source when source.Count() < 3 => "a small enumerable",
    IList<string> list => $"a list with {list.Count} items",
    null => "null",
    _ => "something else"
};
// output: array with a single item

官方文档

我们在这篇文章中只看到了模式匹配表达式的示例。
有关更多详细信息,请查看官方文档:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值