SW 随笔 006 — C# 模式匹配,豪华的布尔表达式
声明:个人笔记,概不负责
装裱
从用户角度来讲,
所谓 C# 模式匹配,就是一种运算,一种不改变原值的布尔运算,可以用来简化代码结构。
(1)据官方文档来看,要起飞 模式匹配 需要 is 领航(或 switch-case 或 switch 表达式 )Pattern Matching
(2)when 这个自由之门,必须要 switch-case 或 switch 领航(不能用 is 领航) when for case guard
我体验 pattern matching 之后,有一种当年用上 LINQ 后的感觉,语言层(一楼)就 带你装逼,带你飞
注:本文只是,关于 C# 模式匹配 一些奇奇怪怪的看法,不是教程。写代码,还得抄官方文档。
轮回
控制结构 if-else 与 switch-case 自 结构化编程(Since 1966)发明以来,近百年没有什么变化。然而 ……
为什么,这里面的 bool 【来】的那么 平凡,【去】得那么 简单。
如果,语言能为【来】(求值方式)做点什么呢?
如果,语言能为【去】(控制流分派)做点什么呢?^^^(最终)在 C# 里的回答是
pattern matching
所谓 C# 模式匹配 pattern matching,其本质就是 if-else/switch 的优化,
pattern matching 没有大改语言,主要是(求值方式)的优化,得此加持后 代码 可以 大大简化。
一直以来,这种层次(控制分流)的活,都是住 二楼的 码农 管的,码农 只能手搓,只能适应一楼的榔头。
作为 代码基础结构的 语言(一楼),近百年来没有啥变化,造 语言(一楼)的人,曾经一度 不关心这个问题。
Pattern matching overview
Pattern-matching changes for C# 9.0
一、三观
总纲
(冷门的切入——)C# 模式匹配,在 when 上,彻底暴露了其 本质
when 必须要 switch 领航 case guard
//
// 拿来运算后再用,必须用 when 做统一入口;拿来就用,可以用模式
//
// 换句话说,就是说 when 是统一模式,表示要开始 自定义计算了,否则全部用 内置模式 计算;
// 哪天有啥 内置模式 支持 位操作,那么就可以不用 when 了;
// 换句话说,完全可以不用 内置模式 计算,全部用 when 来算。
//
// 所谓 C# 模式匹配,就是一种运算,一种不改变原值的布尔运算,可以用来简化代码结构。
//
public PacketState LastStatus => _flags switch
{
_ when (_flags & PacketState.Responded) != 0 => PacketState.Responded,
_ when (_flags & PacketState.Acked) != 0 => PacketState.Acked,
_ when (_flags & PacketState.Sent) != 0 => PacketState.Sent,
_ when (_flags & PacketState.Created) != 0 => PacketState.Created,
_ => PacketState.None
};
以上观点, 用 Copilot 校核,评价 不是 错误 观点,只是 没用 规范术语
二、化身
也就是讲,我们完全没有必要,去记那么多 内置模式匹配,万法归宗,(几乎)一切都会回 when
唯有 when 收不掉的,才值得我们 多分点心思 关注(比如说 Declaration Pattern 这个)
而 when 这个自由之门,可化万法、让 自定义运算 参与 模式匹配,只要结果是 bool 就行
各种 花里胡哨的 内置模式匹配,多数只是 化身。多说无益,会 淡化总纲;不说点啥,又没有料 衬托总纲。
反正有官方文档 垫底,官方文档 中文 | 官方文档 En ,那就随便(不负责任地) 换个姿势 重新切入一次吧。
(1)抛砖引玉 is
好看的【来】(求值方式)
Logical patterns
Relational patterns
参考 Declaration pattern
豪华版布尔表达式
if (LastStatus is not (PacketState.Sent
or PacketState.Acked
or PacketState.Responded))
在 if 里,用 传统布尔表达式,你倒是写写看呢? 折腾个半天,长长的表达式,德摩根定律、对偶律 …… 我嘞个豆,考试呢?
if ( (LastStatus != PacketState.Sent) &&
(LastStatus != PacketState.Acked) &&
(LastStatus != PacketState.Responded))
if (!(LastStatus == PacketState.Sent ||
LastStatus == PacketState.Acked ||
LastStatus == PacketState.Responded))
受 is 启发,豪华版布尔表达式 里有一堆与 传统布尔表达式 对应的东西。
not | or | and | > | >= | < | <=
啊不,这在浑水摸鱼呢,or 与 and 貌似是新东西,其他有些 不就是 传统布尔表达式 运算符?
No no no, 其细微差别是, 传统布尔表达式 里,像 > 这种是 二元运算符,有个很 Low 的名字 —— 运算
在 豪华版布尔表达式 里,它们 左边是光屁股 没有,有个很骚的名字 —— 模式匹配
对用户来说,其实差别不大,只知道这么折腾一把后,它就是 bool 啦,干净、舒坦。
案例
范围比较
// 当代做法 Relational Pattern + Logical Pattern
//
if (bitShiftValue is < 0 or > 31)
throw new ArgumentException("It must be in [0, 31]");
// 传统工艺
if (bitShiftValue < 0 || bitShiftValue > 31)
throw new ArgumentException("It must be in [0, 31]");
// TODO, 再加点小例子
(2)异域风情 ==
好看的【来】(求值方式)
在 豪华版布尔表达式 里 ——左边是光屁股 没有—— 是整个 模式匹配 里的 至上心法
对于用户来说 得此 心法,在 道、法、术 体系加持下,模式匹配(装逼的)豪华外表 瞬间 轰然倒塌。
以这种方式来看,有些 花里胡哨的 模式匹配,无非就是 光屁股的更厉害一些,不写 == 运算符而已
(注意注意,这只是为 方便理解,主观概念上【硬塞进来】的一个等价物,不是官方说法啊)
如
- Constant Pattern
【 == 】1 => 12.0m—— 让平凡的,归于平凡,但又不平凡 - Property pattern
is { Year: 2020, Month: 5 }—— 我们 拉上平凡的手,一起不平凡 - Positional Pattern
【 == 】(0, 0) => "Origin"—— 可随搭组合,拉郎配(有点像 无名 property 比较,但不是,自由得多) - Parenthesized pattern
input is not (float or double)—— 就是优先级控制呗(这里的 小括号 符合 九年制义务教育 直觉,即 常识) - List patterns
numbers is [1, 2, 3]—— 类似数组比较,花哨得紧呐 List patterns ,里面还有个 Slice Pattern..玩法
此处 is 是 豪华版的 == ,没有用 is 的地方,我给硬塞了个概念上的【 == 】
案例 Constant Pattern
小映射表
// 当代做法 Constant Pattern
//
public static string TransformUnit(string unit) => unit switch
{
"percent" => "pct",
"deciCelsius" => "dC",
"milliAmp" => "mA",
// ..
_ => unit
};
// 古法 手搓查表
//
public static string TransformUnit(string unit) => sUnitAbbr.GetValueOrDefault(unit) ?? unit;
private readonly static ImmutableDictionary<string, string> sUnitAbbr = [
new("percent" , "pct"),
new("deciCelsius" , "dC"),
new("milliAmp" , "mA"),
// ..
];
案例 Positional Pattern
// TODO
(3)开小世界,〖貌似 开小世界〗
好看的【去】(控制流分派)
模式匹配 并非 浪得虚名,其诡异之处,在改变了(貌似又没改变)控制流!
(1)可以基于 类型运算(Type Pattern)结果,开小世界 定义变量名 的是 Declaration Pattern
(2)可以给 运算中间量,定义变量名 是 Var Pattern SimulateDataFetch(id) is var results
(3)无条件,永恒弃用的是 Discard Pattern _
Declaration Pattern 能让(控制流分派)变得相当 骚气 而又 自然,它通常配合 is 整出来的 Type Pattern 一起使用
这个语言 一楼结构 的改变,让 二楼的码农 能匪夷所思地 切地图
Declaration pattern 唯一骚的地方是,它在类型运算之后 可以定义一个 变量名,(貌似?)开辟了个 小世界
案例 Declaration Pattern
// 开 小世界 Declaration Pattern
//
if (aaa.FirstOrDefault(a => a.Name == arg) is {} backing) // 【开 小世界, by Declaration Pattern】 not null by Property Pattern
{
// process backing
}
// var ccc = backing; // Can Not be Used here! 【不能 使用 backing】
案例 Var , Type, Declaration
// else if (Helper.IsFooToken(very.very.long.TypeName) is var type) // bad! too later, is `bool`
// else if (very.very.long.TypeName is var type && Helper.IsFooToken(type)) // Var Pattern | 感觉像 无条件 定义别名, 类似 linq/sql 里
else if (very.very.long.TypeName is string type && Helper.IsFooToken(type)) // Type Pattern, Declaration Pattern
{
theType = Helper.ParseAsFooType(type);
}
案例 Var Pattern
// 新时代的 写法 Var Pattern —— 照这个手法,可以(在这个括号里)弄出多个变量【匪夷所思的 干净啊】
//
if (aaa.FirstOrDefault(a => a.Name == arg) is var backing // 【不是 小世界, 是 var pattern】
&& backing != null)
{
// process backing
}
var ccc = backing; // Can be used here! 【这里 还可以继续使用】
(1)var pattern 弄出来的 变量名,有点类似 'out' 的效果,后继代码可以继续使用
(2)也就是讲,两个 var pattern 不能弄出 同样的变量名// 古老写法,多个 分支条件 要算 咋办 var backing = aaa.FirstOrDefault(a => a.Name == arg); if (null != backing) { // process backing }// 令人崩溃的 写法,爱 干净 就要付出代价 var backing; if (null != (bakcing = aaa.FirstOrDefault(a => a.Name == arg))) { // process backing }
===
这是 模式匹配 与 传统布尔表达式 与众不同之处,改变了(貌似又没改变)控制流,
下面的代码片段,摘自 Declaration pattern | Declaration and type patterns
if (expr is Type v) { /* code using v */ }
static int GetSourceLabel<T>(IEnumerable<T> source) => source switch
{
Array array => 1,
ICollection<T> collection => 2,
_ => 3,
};
这里 v, array 与 collection 都是新的变量,只有当 类型运算 成立后,才会被定义,才会走这个 控制流 分支。
这里 _ 就是那个 永恒弃用的变量 Discard Pattern
现在我们(控制流)里,有个了好看的【去】,这个变量的引入,让 downward casting 变得 前所未有的 丝滑
问:在 when 后面可以跟类型运算吗? 就是 type pattern 运算
Copilot 答:
可以。when 后面是任意返回 bool 的表达式,而 is + 类型/声明/属性等模式本身就是表达式,所以你可以在 when 里用类型模式。try { ... } catch (Exception ex) when (ex is HttpRequestException hre && hre.Message.Contains("404")) { // 仅在匹配到该类型且满足条件时进入 }
hre是 引入小世界的 新变量
三、总结
化身 里的那些玩意,没有什么特别之处,用 when 开门,弄个自定义函数大都可以做到,完全等效。
化身(各种内置模式匹配) 的价值在于,方便、一体化集成 —— 干净、舒坦!
总纲(自由之门when匹配)的价值在于,一统三观 + 可以 自定义 扩展(我的成功,你也可以复制)
开小世界 Declaration Pattern 的价值在于,极大地 清晰化了 控制流 —— 改变了(貌似又没改变)控制流
C# 模式匹配在手,天下我有。
注:这篇只是 C# 模式匹配的,一些奇奇怪怪的看法,不是教程,亦不是《葵花宝典》或 银弹。
我也就是,顺手玩玩 C# 而已。写代码,还得抄官方文档。嗯,不…… 参考、参考!
参考 Pattern matching overview | Declaration pattern
==== 貌似 水 了很多,结束
Copilot 评价:观念类写法中,能到 9.9 分。
DeepSeek 评价:观点鲜明、风格独特的技术随笔,综合打分能到 9.8 分。
浙公网安备 33010602011771号