是我还是函数式编程(FP)似乎最近很流行? 诸如Haskell,Elixir和F#之类的FP语言比以往任何时候都更强大,并且大型框架尝试采用一种更具功能性的方法。 甚至C#都尝试在语言中包含功能构造。
实际上,最新版本的C#(8.0)中最大的功能之一就是模式匹配。 模式匹配在C#中并不是真正的新事物。 到目前为止,这是非常基本的,并且感觉像是二等公民。 在最新版本的C#中,我们现在对此提供了全面的支持。
让我们潜入吧!
模式匹配是通过使用switch
运算符实现的。 请稍等...该语言是否已不存在switch
? 是的,这使得采用此新功能非常混乱。
即使switch
运算符的关键字,语法和行为相似,新功能也会显着不同。 最大的区别之一是新switch
是一个表达式,旧switch
是一个语句。 您不再有case和break语句。
表达与陈述
在我们继续之前的快速注释。 对于大多数人来说,语句和表达式之间的区别非常不清楚。 这是到目前为止我找到的最好的定义。
表达式 :计算时返回值的东西
语句 :执行某件事的一行代码
例如
声明
if (a == null )
{
someCode...
}
else
{
someCode2...
}
表达
a == null ? value1 : value2;
话虽如此
话虽如此,让我们探讨这两个switch
操作符之间的异同。
旧开关
句法
switch ( value )
{
case 1 : something...
break ;
case 2 : something2...
case 3 : something3...
break ;
default :
somethingDefault....
break ;
}
旧的开关试图找到下一条要分支的指令。 然后,它将依次执行每一行,直到命中break语句为止。 它的行为就像goto
语句,通常被认为是不好的做法。 该运算符仅运行代码,不返回任何内容,因此将其视为语句。
新开关
句法
var a = value1 switch
{
matchingExpression => expression
matchingExpression2 => expression
...
}
另一方面,新开关必须返回一个值。 实际上,每个可能的匹配都必须返回相同类型的值。 匹配表达式可以使用模式来匹配或不匹配。 因此,名称模式匹配。
模式的力量
匹配模式不仅在条件为true时匹配,还将对象转换为匹配的类型并将其自动分配给变量。 从那里,您可以匹配对象属性,甚至可以对那些属性评估布尔表达式。
没有比这更好的例子了
让我们探索一个具有许多业务规则的TollService
应用程序。 我们将看到模式匹配如何使代码清晰易读。
V1-类型匹配
public decimal CalculateToll ( object vehicle )
{
// Price per vehicle type
// ==========
// Car -> 2$
// Taxi -> 3.50$
// Bus -> 5$
// Truck -> 10$
return vehicle switch
{
Car c => 2.00 m,
Taxi t => 3.50 m,
Bus b => 5.00 m,
DeliveryTruck t => 10.00 m,
{ } => throw new ArgumentException( "Not a known vehicle type" , nameof (vehicle)),
null => throw new ArgumentNullException( nameof (vehicle))
};
}
V2-属性匹配
您可以使用{}
匹配对象的特定值
public decimal CalculateToll ( object vehicle )
{
// Price considering occupancy
// ===========
// Car and taxi
// No passengers -> +0.50 $
// 2 passengers -> -0.50 $
// 3 or more passengers -> -1$
return vehicle switch
{
Car { Passengers: 0 } => 2.00 m + 0.50 m,
Car { Passengers: 1 } => 2.0 m,
Car { Passengers: 2 } => 2.0 m - 0.50 m,
Car _ => 2.00 m - 1.0 m,
Taxi { Fares: 0 } => 3.50 m + 1.00 m,
Taxi { Fares: 1 } => 3.50 m,
Taxi { Fares: 2 } => 3.50 m - 0.50 m,
Taxi _ => 3.50 m - 1.00 m,
Bus b => 5.00 m,
DeliveryTruck t => 10.00 m,
{ } => throw new ArgumentException( "Not a known vehicle type" , nameof (vehicle)),
null => throw new ArgumentNullException( nameof (vehicle))
};
}
V3-条件布尔表达式
您可以使用when
关键字定义将在对象上评估的布尔表达式。 您可能已经熟悉when
关键字。 它是先前在C#6.0中的catch
语句中引入的,用于评估异常属性的条件。 您可以想象,它在这里的工作方式完全相同,因此您不会迷路。
public decimal CalculateToll ( object vehicle )
{
// Price considering occupancy
// ===========
// Bus
// Less than 50% full -> +2$
// More than 90% full -> -1$
return vehicle switch
{
// car and taxi hidden here to make it easier to read
// ...
Bus b when ( double )b.Riders / ( double )b.Capacity < 0.50 => 5.00 m + 2.00 m,
Bus b when ( double )b.Riders / ( double )b.Capacity > 0.90 => 5.00 m - 1.00 m,
Bus _ => 5.00 m,
DeliveryTruck t => 10.00 m,
{ } => throw new ArgumentException( "Not a known vehicle type" , nameof (vehicle)),
null => throw new ArgumentNullException( nameof (vehicle))
};
}
V4-嵌套
switch
运算符必须返回一个表达式,并且由于它们本身就是表达式,这意味着我们可以嵌套它们以使我们的代码更易于阅读。
public decimal CalculateToll ( object vehicle )
{
return vehicle switch
{
Car c => c.Passengers switch
{
0 => 2.00 m + 0.5 m,
1 => 2.0 m,
2 => 2.0 m - 0.5 m,
_ => 2.00 m - 1.0 m
},
Taxi t => t.Fares switch
{
0 => 3.50 m + 1.00 m,
1 => 3.50 m,
2 => 3.50 m - 0.50 m,
_ => 3.50 m - 1.00 m
},
// Bus and truck hidden here to make it easier to read
// ...
};
}
特殊匹配表达式
您可能已经注意到在前面的示例中,我们一直在使用一些特殊的匹配表达式{}
, null
和_
。
-
{}
:非null,但类型未知 -
null
:空值 -
_
:绝对匹配任何通配符,但不捕获新变量中的值
Gotcha
匹配表达式从上到下进行评估。 始终将限制性最强的匹配表达式放在首位,并尝试以“特殊”匹配表达式结束。
例如,如果您使用 _
作为第一行,则它将始终在此表达式上匹配,并且不会评估其他任何内容。
结束语
模式匹配功能非常强大,可用于简化复杂的业务逻辑,同时使其更易于阅读。 请继续关注本系列的更多博客文章!
所有代码示例都可以在github上找到
参考文献
先前发布在https://blog.miguelbernard.com/pattern-matching-in-csharp/
From: https://hackernoon.com/whats-pattern-matching-in-c-80-6l7h3ygm