SW 随笔 006 — C# 模式匹配,豪华的布尔表达式

声明:个人笔记,概不负责

装裱

从用户角度来讲,

所谓 C# 模式匹配,就是一种运算,一种不改变原值的布尔运算,可以用来简化代码结构。

(1)据官方文档来看,要起飞 模式匹配 需要 is 领航(或 switch-caseswitch 表达式 )Pattern Matching
(2)when 这个自由之门,必须要 switch-case 或 switch 领航(不能用 is 领航) when for case guard

我体验 pattern matching 之后,有一种当年用上 LINQ 后的感觉,语言层(一楼)就 带你装逼,带你飞

注:本文只是,关于 C# 模式匹配 一些奇奇怪怪的看法,不是教程。写代码,还得抄官方文档。

轮回

控制结构 if-elseswitch-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 | > | >= | < | <=

啊不,这在浑水摸鱼呢,orand 貌似是新东西,其他有些 不就是 传统布尔表达式 运算符?
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)异域风情 ==

好看的【来】(求值方式)

在 豪华版布尔表达式 里 ——左边是光屁股 没有—— 是整个 模式匹配 里的 至上心法

对于用户来说 得此 心法,在 道、法、术 体系加持下,模式匹配(装逼的)豪华外表 瞬间 轰然倒塌。

以这种方式来看,有些 花里胡哨的 模式匹配,无非就是 光屁股的更厉害一些,不写 == 运算符而已
(注意注意,这只是为 方便理解,主观概念上【硬塞进来】的一个等价物,不是官方说法啊)

  1. Constant Pattern 【 == 】1 => 12.0m —— 让平凡的,归于平凡,但又不平凡
  2. Property pattern is { Year: 2020, Month: 5 } —— 我们 拉上平凡的手,一起不平凡
  3. Positional Pattern 【 == 】(0, 0) => "Origin" —— 可随搭组合,拉郎配(有点像 无名 property 比较,但不是,自由得多)
  4. Parenthesized pattern input is not (float or double) —— 就是优先级控制呗(这里的 小括号 符合 九年制义务教育 直觉,即 常识)
  5. List patterns numbers is [1, 2, 3] —— 类似数组比较,花哨得紧呐 List patterns ,里面还有个 Slice Pattern .. 玩法

此处 is 是 豪华版的 == ,没有用 is 的地方,我给硬塞了个概念上的【 == 】

参考 Declaration pattern

案例 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)开小世界,〖貌似 开小世界〗

好看的【去】(控制流分派)

参考 Declaration pattern
Declaration and type patterns

模式匹配 并非 浪得虚名,其诡异之处,在改变了(貌似又没改变)控制流!

(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, arraycollection 都是新的变量,只有当 类型运算 成立后,才会被定义,才会走这个 控制流 分支。
这里 _ 就是那个 永恒弃用的变量 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 分。

posted @ 2025-12-30 22:54  悠洋洋  阅读(16)  评论(0)    收藏  举报