恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

C# 9.0核心特性:模式匹配与nint原生整数实战指南

  • 首页
  • 资讯中心
  • /
  • C# 9.0核心特性:模式匹配与nint原生整数实战指南

相关资讯

DDS波形发生器设计与排障:从原理到工程实践 2026/10/14 18:19:19
OpenBot开源导航机器人:SLAM建图+路径规划+ROS控制全链路实现 2026/10/14 18:19:19
有刷电机与无刷电机对比:原理、选型与调试实战指南 2026/10/14 18:19:19

最新资讯

JDBC核心机制与实战指南:从驱动加载到连接池优化
TensorFlow Lite轻量化部署实战:从模型转换到移动端集成
做展示设计,别让开题报告把方案灵感磨没了 ✨
Private VLAN实验详解:从隔离原理到交换机组网配置与验证
多Agent账户管理:如何用 standards-sdk 快速创建 HCS-15 Petal 账户,一文搞定资金管理
Spring事务全解:传播行为、失效场景与分布式方案

今日推荐

IBM Verify Identity Access 修复 22 个漏洞:两个 9.8 严重 RCE 无需认证
小区物业管理系统
小型超市仓库管理系统

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

C# 9.0核心特性:模式匹配与nint原生整数实战指南

发布时间:2026/10/14 18:19:19
C# 9.0核心特性:模式匹配与nint原生整数实战指南 C# 9.0 是 2020 年 11 月跟着 .NET 5 一起正式发布的。我身边不少朋友第一反应是“哦又一个大版本”但真正上手之后会发现这一代最值得玩味的不是那些宏大特性而是两个看起来不大的东西Pattern matching 的补全以及 nint 这个原生大小整数。一个是把条件判断从“堆 if 链”变成“说人话”一个是把平台相关的整数类型从“能用”变成“好用”。如果你平时写过一长串 if-else、switch 里塞满 case或者碰过 Win32/COM 句柄、写过跨平台性能敏感代码那这两个特性跟你的关系最大。这篇文章我不打算罗列特性清单而是直接拆解这两个东西的底层逻辑、使用姿势和实际项目里的避坑经验。尤其会回答几个你八成会关心的问题关系模式和 is not null 到底好在哪nint 和 IntPtr 到底是什么关系为什么业务代码里我建议你少碰 nint1. 为什么这两个特性值得你花时间1.1 模式匹配从“到处 if”到“说出规则”模式匹配这个概念说穿了就是把三件事合并成一句话检查对象长得像什么、从里面取出关键值、按照条件走不同分支。C# 7.0 第一次引入了 is 模式和 switch 模式当时只能做类型判断和简单的常量匹配C# 8.0 加了属性模式和 switch 表达式能把一个对象的结构拆开来看到了 C# 9.0终于把关系运算符和逻辑运算符引进了模式语言里。于是很多以前要写七八行 if 的代码现在能压成一行。比如最典型的年龄判断// 老写法 if (age 18 age 60) { Console.WriteLine(成年人); } // C# 9.0 写法 if (age is 18 and 60) { Console.WriteLine(成年人); }看着只是把 换成了 and但意义完全不同。后者不是一个“布尔表达式”而是一个模式它可以直接塞进 switch 表达式、可以跟其他模式组合、可以让编译器帮你穷尽检查。分支一多差距就出来了后面我会用具体案例说明。1.2 nint被低估的“原生大小整数”nint 在 C# 9.0 里是作为“原生大小整数类型”出现的它在 32 位进程里等于 32 位在 64 位进程里等于 64 位。听起来简单背后解决的是一个长期痛点过去我们要处理平台相关的整数只能用 IntPtr 这种“指针大小整数”。IntPtr 的问题在于它是一个结构体不是一等公民。你想让两个句柄做减法、想判断一个返回值是不是 -1、想把它当数组索引用都得先手动转成 int 或 long写起来像穿着雨靴跑步。nint 则是编译器一等支持的类型能直接做算术、能比较、能当常量、能被模式匹配。更重要的是它是 System.IntPtr 在运行时层面的同一类型所以在互操作场景里不会多出额外的封送开销。我个人的看法是nint 不适合作为业务领域的通用整数但它非常适合三类地方——互操作句柄、平台相关的内存/偏移计算、以及需要在极大数据规模下使用索引的底层库代码。这一点后面展开讲。2. Pattern matching 新玩法逐个拆解2.1 关系模式用比较运算符做分支C# 9.0 允许、、、直接出现在模式里这就是关系模式。它最常见的用法是划定一个数值区间并且跟逻辑模式组合static string GetLevel(int score) score switch { 90 优秀, 75 良好, 60 及格, _ 需要补考 };注意这里没有没有score 60 score 75模式匹配的语义是自上而下匹配第一个符合的规则生效所以每个分支只需要写一个边界即可。这比 if 链清晰得多也避免了边界条件写错。关系模式适用于所有实现了比较运算的类型数字、字符、枚举、DateTime 都可以// 判断一个字符落在哪个区间 char c m; if (c is a and z) { Console.WriteLine(小写字母); }这里有个细节很多人第一次会踩is 90这种写法模式里的表达式必须是一个常量不能是变量。你不能写score is minScore and maxScore编译器会拒绝。如果你需要跟变量比较老老实实用score minScore score maxScore。2.2 逻辑模式and、or、not 三个关键字逻辑模式是这一代模式匹配真正“觉醒”的部分。它把布尔运算下沉到了模式语言里允许你在一处同时表达多个条件。最常见、也最值得立刻用进现有代码的是is not nullif (obj is not null) { // 以前的 obj ! null 写法被替代 }为什么说它比! null好因为!是运算符你可以在类上重载它一旦重载比较的语义就不一定是“引用不等”。而is not null走的是模式匹配的规则编译器按照“空引用判定”来处理不管你的类型有没有重载!结果都是可靠的。这对那些自我感觉良好、重载过和!的 DTO 尤其重要它能防止你被“看起来对”的判断坑到。or和and可以组合出很直观的区间// 判断一个数是否是合法端口号 if (port is 0 and 65535) { // 合法 } // 判断一个字符是否是元音 if (ch is a or e or i or o or u) { Console.WriteLine(是元音); }注意优先级not的优先级最高然后是and最后是or。所以a is not null and 0不会被解析成a is (not null and 0)而是(a is not null) (a is 0)。如果逻辑复杂该加括号就加括号别赌自己和别人都记得优先级。另外or模式在 switch case 里特别省事。以前你要合并多个 case得写case 1: case 2:现在一个分支搞定string result code switch { 1 or 2 or 3 输入错误, 4 or 5 权限不足, _ 未知错误 };2.3 扩展属性模式不用再叠罗汉C# 8.0 的属性模式允许你这么写user is { Address: { City: 上海 } }结构一深括号叠括号。C# 9.0 引入了扩展属性模式可以直接把属性访问链写进模式里// C# 8.0 if (user is { Address: { City: 上海 } }) { } // C# 9.0 if (user is { Address.City: 上海 }) { }这个特性对那种“对象套对象”的校验场景极其友好。比如判断订单的收货地址是不是海外if (order is { Customer.Address.Country: not CN }) { // 海外订单走另一个流程 }有人会担心如果 user 是 null或者 Address 是 null这样写会不会抛 NullReferenceException不会。模式匹配在求值属性链时任何一级引用为 null都会让整个模式不匹配而不是抛异常。这一点非常安全也是我愿意在复杂嵌套对象上大量使用它的原因。2.4 模式匹配与 switch 表达式组合才是终极形态关系模式、逻辑模式、属性模式单个看都只是语法糖真正威力大的是它们互相嵌套然后配合 switch 表达式一次完成整个分支。举个实际点的例子根据角色和状态计算订单折扣decimal GetDiscount(Order order) order switch { { Role: VIP, Total: 10000 } 0.8m, { Role: VIP } 0.9m, { Role: Normal, Total: 5000 } 0.95m, { Role: Normal } 1.0m, _ throw new InvalidOperationException(未知角色) };这种代码的可读性比起一串 if 嵌套完全不是一个体验。每个分支就是一个“规则行”从上往下看规则一目了然。而且以后要加规则只需要加一行不用去翻大括号。使用 switch 表达式时还有一个重要原则永远保留一个_兜底分支。因为关系模式、逻辑模式混合使用时编译器不总是能证明你的分支已经穷尽所有情况留着_既是给编译器交代也是给未来加需求留后路。3. nint/nuint 原生大小整数实战解析3.1 nint 到底是什么和 int、long、IntPtr 什么关系先用一个问题引入你在写一个跨平台库要跟外部系统交换“地址大小”或“句柄”应该用什么类型用 int32 位上没问题64 位上可能截断用 long32 位上浪费一倍内存和寄存器带宽。你需要的是一个“跟着进程位数走”的整数类型这就是 nint。nint 的运行时类型实际上就是 System.IntPtr。你可以把 nint 理解为 IntPtr 的“编译器友好外壳”维度intlongIntPtrnint位数固定 32 位固定 64 位跟随平台跟随平台算术运算支持支持需先转换直接支持作为常量支持支持不支持旧版支持模式匹配支持支持类型判断直接支持P/Invoke 默认封送intlongIntPtrIntPtr 语义从表格能看出来nint 的出现就是要取代“把 IntPtr 转来转去”的日常操作。你在代码里写nint和在运行时看到IntPtr是同一回事所以互操作边界上传入传出时封送程序完全按 IntPtr 处理不会多一次转换。nuint 是 nint 的无符号版本用于表示无符号的平台大小整数。用法和注意点跟 nint 对称需要无符号场景时选它即可。3.2 nint 的运算、转换与常量nint 的声明和普通整数几乎没区别nint code -1; nuint size 0; // 参与算术 nint offset 100; offset 200; nint next offset * 2;它直接支持、-、*、/、%、位运算、比较运算这些操作在编译器层面会被优化成对应平台位宽的原生整数运算不会像 IntPtr 那样每个操作都先调方法。常量场景要注意一个区别。你可以这样写const nint MaxRetryCount 3;只要这个值在当前编译目标平台上放得下就能作为常量正常使用。但如果你写了一个很大的数值比如const nint BigValue 5000000000;它在 64 位平台上没问题拿到 32 位平台上编译会直接报错因为这超出了 32 位 nint 的表示范围。这就是平台相关类型在编译期给你上的一课。转换规则很简单int 可以隐式转换到 nintnint 隐式转换到 long前提是目标平台位数不超过 long实际都如此反方向 nint 转 int 是显式强转可能丢精度。跨平台序列化时要特别小心下面我会展开讲。3.3 典型场景一互操作句柄处理做 Win32 或 COM 互操作时一个很常见的模式是外部函数返回一个 “句柄”无效时返回 -1 或 0。以前你得先拿到 IntPtr再转成 int 判断或者用IntPtr.Size自己按平台分支。现在 nint 让这件事变得非常直接// 模拟外部函数返回句柄 nint handle OpenDevice(device, 0); if (handle is -1 or 0) { throw new InvalidOperationException(打开设备失败); }注意handle is -1 or 0这种写法nint 搭配模式匹配相当顺滑编译器能正确推导。如果你用 IntPtr写handle.ToInt64() -1之类的代码读起来就差远了。我还习惯在互操作层用 nint 做“固定大小”的状态打包。比如在 64 位进程里把一个指针和一个状态位打包到一个 nint 里用位运算快速读取nint packed (nint)ptr | statusFlag; nint apiResult CallNative(packed);这对性能敏感而且依赖平台位宽的底层模块非常实用。3.4 典型场景二平台相关的内存与边界计算nint 的第二个大本营是内存/缓冲区相关的代码。假设你写一个自定义缓冲池需要根据平台位数决定最大容量同时要快速判断一个写入偏移是否越界nint bufferSize GetBufferSize(); nint writeOffset GetWriteOffset(); if (writeOffset is 0 and bufferSize) { // 安全写入 } else { throw new ArgumentOutOfRangeException(nameof(writeOffset)); }关键在于bufferSize 来自本机环境用 nint 保存能确保在 32 位和 64 位上都能完整表达同时避免在 32 位平台上使用 long 带来的性能浪费。边界判断用关系模式写代码意图一眼就懂。这里有个实用技巧我一般在内存管理类里写工具方法用 nint 做内部计算但对外只暴露 long 或 int。这样内部可以安心匹配平台位数外部 API 的语义保持稳定。3.5 典型场景三性能敏感的大循环索引写大型数组、位图、音频采样缓冲这类需要高性能遍历的底层代码时如果数据规模可能超过 20 亿int.MaxValue你自然想用 long但在 32 位进程中long 运算会拆成两个 32 位寄存器操作速度上吃亏。用 nint 则既能跟上平台位数又不会做多余的高位运算nint totalSamples GetTotalSamples(); for (nint i 0; i totalSamples; i) { // 处理每个采样点 }这种用法在音频/视频处理、大量采样数据的批处理库里很常见。普通业务开发用不上但一旦用上效果立竿见影。3.6 nint 的坑提前说清楚先泼一盆冷水nint 不是让你在业务代码里到处用的类型。我自己踩过的坑以及帮别人 review 时看到的坑大概有这么几类。第一DTO 和序列化别用 nint。同样是nint在 32 位环境和 64 位环境里得到的字符串不一样、占用空间不一样一旦数据从 64 位平台序列化到 32 位平台可能直接溢出或者解析失败。跨进程、跨机器、跨架构传输数据时一定要用固定宽度的 long 或 int。第二默认不检查溢出。nint 的算术运算在默认上下文里是 unchecked 的也就是说溢出时不会抛异常直接截断。如果你在做地址运算一次溢出可能就是一次内存破坏。需要安全的时候请主动用checked块包住关键计算。第三泛型里使用要小心。C# 9 里 nint 在泛型上下文的支持有限你没法像Listint那样把 nint 直接当泛型实参使用后续版本有改进但为了迁就老目标框架我仍然建议用 IntPtr 或者 long 做泛型载体。第四跟 IntPtr 互转要心里有数。虽然两者运行时是同一个类型但是当你写IntPtr value someNint;或者说把 nint 传给只接受 IntPtr 的接口时编译器有时会提示需要转换因为语言层面的身份并不完全一致。遇到这种提示别奇怪强转一下就好。4. 实战案例把两个特性合起来用4.1 案例一业务校验规则重写下面是我在模拟项目里真实做过的重构。需求是根据用户的会员等级、订单金额、以及是否是新用户决定优惠方案。老代码是这样的if (order.Amount 1000) { if (user.Level VIP !user.IsNew) return 0.8m; if (user.Level VIP) return 0.85m; return 0.95m; } if (order.Amount 500) { if (user.Level VIP) return 0.9m; return 0.97m; } return 1.0m;用 C# 9.0 重写后return order switch { { Amount: 1000, User: { Level: VIP, IsNew: false } } 0.8m, { Amount: 1000, User.Level: VIP } 0.85m, { Amount: 1000 } 0.95m, { Amount: 500, User.Level: VIP } 0.9m, { Amount: 500 } 0.97m, _ 1.0m };每一行就是一个独立的业务规则优先级从上往下后续加规则只需要加一行。模式匹配嵌套属性的能力在这里体现得淋漓尽致金额判断用关系模式属性取值用扩展属性模式条件的优先级靠代码顺序表达。肉眼可见地好维护。4.2 案例二互操作句柄与状态判断再来看一个把 nint 和模式匹配结合得更紧密的示例。假设你封装了一个底层库函数返回的结果同时包含“是否成功”和“操作类型”的信息用 nint 打包返回nint result DoNativeOperation(); string message result switch { 0 $调用失败错误码 {result}, 0 or 200 成功, 200 and 999 业务异常, _ 未知状态 };这里result是 nint但又用了关系模式和逻辑模式来分支。如果你是 64 位程序这个 nint 有机会承载更大的错误码范围如果是 32 位程序也能保证跟平台的 api 语义一致。互操作代码最怕平台差异这样写至少能消除因为 int 宽度不够导致的隐性 bug。4.3 案例三环形缓冲区索引推进最后一个案例是个典型的底层数据结构环形缓冲区。用 nint 维护读写索引同时用模式匹配检查异常状态nint head 0, tail 0, capacity 1024; // 写入一个元素 void Write(byte b) { if (IsFull()) { throw new InvalidOperationException(缓冲区已满); } buffer[tail] b; tail (tail 1) % capacity; } bool IsFull() (tail 1) % capacity head; // 判断数据长度 nint Count() tail head ? tail - head : capacity - head tail;这里的头尾索引非常适合用 nint容量来自初始化时传入的平台相关值索引推进要用取模运算计算数据长度又要做大小比较。用了 nint 后整个环形缓冲区的位宽天然跟平台一致不会因为索引类型偏窄而限制容量也不会因为偏宽而在 32 位平台上白白损失性能。如果你想检查环形缓冲区的“半满”状态还可以直接写if (Count() is 0 and capacity / 2) { // 未过半 }模式匹配对 nint 同样生效两种新特性的组合在底层代码里意外地协调。5. 常见问题与排查技巧实录5.1 Pattern matching 常见翻车点第一个最常见的翻车是优先级记混。not高于and高于or我是吃过亏的。比如我写if (x is not null and 0)一开始以为它表达的是“x 不是 null并且 x 大于 0”确实如此因为not null整体是一个模式and 0是另一个模式。但如果你写if (x is not (null or 0))这里的括号就不能省否则语义完全变了。第二个是 switch 表达式里忘记写_。当你的模式包含 0 or 100这种区间时编译器不一定能断定你已经覆盖所有整数不写_会直接编译失败。我建议统一养成写_的习惯毕竟未来加一个分支的时候兜底分支能兜住意外情况。第三个坑是可变值参与模式匹配的不可变性。前面提过关系模式右边必须是常量。很多人想用变量当边界写成if (score is passLine and fullMark)这段代码编译不过。你需要换个思路要么把变量转换成常量表达式参与的模式要么干脆退回普通比较。这不是 bug是模式匹配的设定早点接受比较好。第四个是属性模式下的变量捕获。or模式的两个分支不能声明同名变量比如is ( 0 or -10) var v这种组合很容易报“变量范围冲突”。遇到复杂情况我宁可拆成两个 if或者在 switch 表达式里用递归模式别硬写。第五个坑是关于可空值类型。对int?做is 5时模式匹配先判断有没有值再比较。如果值是 nullis 5会返回 false不是异常。这个行为符合直觉但如果你用传统x.HasValue x.Value 5习惯了可能还会愣一下。没关系这种写法其实是更安全的。5.2 nint 常见问题实录我在实际项目里见过最典型的 nint 翻车是在一个平台无关的公共类库里定义了 nint 类型字段结果被 32 位进程引用后反序列化的数据直接错位。查了半天发现是 64 位进程写出的数据nint 占 8 字节32 位进程读入时按 4 字节解析。所以再强调一次nint 只能活在进程内部别出边界。第二个问题是nint和IntPtr在重载决议时的混淆。某些 API 同时有接收 nint 和接收 long 的重载写调用点时要小心到底调了哪个。特别是在老库和 P/Invoke 签名混用的时候编译器不见得按你直觉选重载。遇到这种问题明确强转DoSomething((nint)result);第三个问题是 32 位平台上的溢出静默。我以前写过一个地址累加器在 x64 上跑得好好的拿到 x86 测试机上数据就错乱就是因为 nint 在 32 位平台上一累加就溢出了还没有任何异常。排查这种问题最快的方法是给关键计算包上checked块让溢出瞬间暴露出来nint next checked(head count);第四个问题跟调试有关。你会在调试器里看到 nint 显示成 IntPtr 的样子有时还会带个十六进制前缀。这是正常的因为它运行时就是 IntPtr。别为了让它“看起来像整数”专门转成 int 去看那样反而看不到完整值。5.3 排查技巧速查表现象可能原因处理方式is x编译失败x 是变量关系模式右侧必须是常量改用传统比较或把变量提升为局部常量switch 表达式提示不穷尽区间模式让编译器无法验证覆盖增加_兜底分支or分支里变量冲突模式分支变量范围冲突拆分判断或改用嵌套模式32/64 位切换后 nint 值异常nint 宽度的平台差异跨平台传输改用 long内部计算包 checkedP/Invoke 参数类型不匹配nint 与 IntPtr 语言层面身份差异强转为所需类型后再传参泛型类型参数用了 nint 报错C# 9 时期泛型约束不完善改用 IntPtr / long 作为泛型参数6. 写在最后的一点经验分享我在项目里推广 C# 9.0 这两个特性时遵循的原则是“先易后难、边界隔离”。最容易先做的是把所有! null替换成is not null这一步风险低、收益直观还能让代码在后续模式匹配重构时更顺滑。第二步是把多分支的 if-else 链改写成 switch 表达式优先挑那些分支里逻辑不重的场景练手。最后才在互操作和底层内存管理代码里引入 nint。关于 nint我最后的建议是在公共 API 上尽量别露它内部用 nint 计算对外暴露长整型或者明确宽度的类型。这样即使未来换平台也不会让调用方一头雾水。如果你在一个类库里维护跨平台逻辑一定要在你的持续集成流水线里同时跑 x86 和 x64 两套测试nint 相关的边界问题几乎只在某个具体平台上出现少跑一套都等于在埋雷。这两个特性看起来不大但确确实实改善了我日常写代码的体感。模式匹配让“规则”在代码里变得可见、可读、可维护nint 让平台相关的整数操作从“别扭”变成了“顺手”。如果你还没在真实代码里试过建议找个周末拿个老模块重构一下那种“原来可以这么写”的感觉只有亲自试过才知道。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号