C#中可以通过 is 或者 switch 匹配各种各样的Pattern。
Pattern主要类型:
- Declaration pattern: 运行时的表达式类型,并给一个Local Variable赋值
- Type pattern:也是运行时表达式类型
- Constant pattern: 监测表达式结果是否等于一个常量
- Relational pattern: 监测表达式结果是否大于or小于一个常量
- Logical pattern: 监测表达式是否满足一系列的逻辑判断
- Property pattern:监测表达式的字段或者属性是否满足
- Position pattern: 析构表达式结果并做判断
- Var pattern: 匹配任意表达式并将结果赋给一个已经声明的变量
- Discard pattern: 用来匹配其他的情况,全收
- List patterns:用来判断一个系列的元素是否满足
主要使用的方式:
- is expression
- switch statement/switch expresssion
is 没有switch使用方式灵活, 用is写得我目前觉得应该都能用switch替换掉,is主要用在以下场合:
- 检查运行时的表达式类型,当declaration type为T时
- run-time pattern 也为T
object greeting = "Hello, World!"; if (greeting is string message) { Console.WriteLine(message.ToLower()); // output: hello, world! }
- run-timie pattern 从T中继承,是接口T的实现, 或者其他隐式引用类型转化,举例
var numbers = new int[] { 10, 20, 30 }; Console.WriteLine(GetSourceLabel(numbers)); // output: 1 var letters = new List<char> { 'a', 'b', 'c', 'd' }; Console.WriteLine(GetSourceLabel(letters)); // output: 2 static int GetSourceLabel<T>(IEnumerable<T> source) => source switch { Array array => 1, ICollection<T> collection => 2, _ => 3, };
output:
-
run-time type 是nullable T
- 可以通过封箱或者拆箱进行转化
- run-time pattern 也为T
- 检查null
- 从C# 9.0 检查 non-null
- 检查是否满足list pattern
is的主要使用方式举例:
int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
Console.WriteLine(a + b); // output: 30
}
分别对Xnullable和yBoxed进行判断,结果为真之后赋值给Local Variable a和b。
switch在整体性上更胜一筹,举例来说,我有一个多种不同车辆的收费app,pattern match的优势在于这种不是从同一个base class继承而来的多个体系,判断会很方便:
车辆类:
namespace ConsumerVehicleRegistration
{
public class Car
{
public int Passengers { get; set; }
}
}
namespace CommercialRegistration
{
public class DeliveryTruck
{
public int GrossWeightClass { get; set; }
}
}
namespace LiveryRegistration
{
public class Taxi
{
public int Fares { get; set; }
}
public class Bus
{
public int Capacity { get; set; }
public int Riders { get; set; }
}
}
Caculator 类
namespace Calculators;
public class TollCalculator
{
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car c => 2.00m,
Taxi t => 3.50m,
Bus b => 5.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
}
这里也初始化了本地变量 c t b 但是其实么有用到,所以也可以省略掉,写为:
namespace Calculators;
public class TollCalculator
{
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car => 2.00m,
Taxi => 3.50m,
Bus => 5.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
}
可以加上vechicle字段或者属性作为判断条件的一部分,比如我假设Car类在乘客大于5的时候才会收费: Car {Passengers:>=5} c => 2.00m,
namespace Calculators;
public class TollCalculator
{
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car {Passengers:>=5} c => 2.00m,
Taxi t => 3.50m,
Bus b => 5.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
}
另外注意是switch的结果是可以作为右侧表达式的,举例来说:
var shape = GetShape();
var area = shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
_ => throw new ArgumentException("Unknown shape")
};
总结来说,switch相对于is的优势:
- switch是个single struct,整体性更好
- switch可以更好地覆盖所有的情况,为特殊情况提供解决方式
- switch可以在属性等一些更复杂的pattern matching情况进行使用
- switch可以作为expression的一部分