惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
S
SegmentFault 最新的问题
Engineering at Meta
Engineering at Meta
量子位
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google DeepMind News
Google DeepMind News
博客园_首页
云风的 BLOG
云风的 BLOG
月光博客
月光博客
I
InfoQ
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Vercel News
Vercel News
美团技术团队
Microsoft Security Blog
Microsoft Security Blog
P
Proofpoint News Feed
D
Docker
F
Fortinet All Blogs
N
Netflix TechBlog - Medium
博客园 - 叶小钗
Martin Fowler
Martin Fowler
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
J
Java Code Geeks
Microsoft Azure Blog
Microsoft Azure Blog

博客园 - 张善友

.NET 11 性能全解读:从 JIT 到基础库,这一版到底快了多少 .NET 11 性能深度解读:这一次,真的「到 11」了 把 LLM 密钥从环境变量里解放出来:OpenClaw.NET 迎来 Vault/OpenBao 密钥后端 RedNb.Nacos 2.0.0 正式发布:.NET 接入 Nacos 3.2.4,AI Registry 全能力落地 Rust 成为微软一线语言之后:谈谈 C# 与 Rust 的互补性 .NET 异常处理的"暗门":代码里写满 catch,你依然能抓住它——从一个 AI Agent 运行时的源码说起 写给 C++ 工程师的 OpenClaw.NET 上手指南:用你熟悉的 C++ 思维,跑起一个生产级 AI Agent .NET 11 RC1 发布:拿到"准生证",生产环境可以上了! 写给 PHP 工程师的 OpenClaw.NET 上手指南:用你熟悉的 PHP 思维,跑起一个生产级 AI Agent 写给 Rust 工程师的 OpenClaw.NET 上手指南:用你熟悉的 Rust 思维,跑起一个生产级 AI Agent 从对标 Java 到对标 Go:Native AOT 的"无痛化"之路,走到哪一站了? NuGet 半年度总结:周下载量从 54 亿到 67 亿,.NET 生态的"新一轮增长期"实锤了 写给 Java 工程师的 OpenClaw.NET 上手指南:用你熟悉的 Spring 思维,跑起一个生产级 AI Agent 写给 TypeScript 工程师的 OpenClaw.NET 上手指南:用你熟悉的 TS 思维,跑起一个生产级 AI Agent 写给 Python 工程师的 OpenClaw.NET 上手指南:用你熟悉的 Python 思维,跑起一个生产级 AI Agent 写给 Golang 工程师的 OpenClaw.NET 上手指南:用你熟悉的 Go 思维,跑起一个生产级 AI Agent MetaSkill 落地 .NET:当 Agent 从「调用工具」进化到「组织工具」 都是 AI 写代码,为什么 C# 比 Java 快半拍 MHS 三部曲(下):谁允许 AI 行动?——权力、合规与中国厂商的答卷 弱模型不能裸奔:Agent Harness 凭什么真实有效 MHS 三部曲(中):8 小时集成、六种被拦截的故障,和一次教科书级的翻车 TensorSharp 3.3.0.0 发布,视频生成、DFlash2 投机解码、安全加固一起来了 MHS 三部曲(上):别急着叫它「物理 MCP」——Anthropic 到底发布了什么 编程语言的「第三条道路」上,走得最远的其实是 C# 纯 .NET 手写 CUDA kernel,GLM-5.3-Flash decode 跑出 llama.cpp 的 2 倍 3 张卡到底能不能跑大模型推理?从 vLLM、llama.cpp 到 TensorSharp 的多卡真相 AI 编程时代,.NET 的机会在哪里? Vibe Coding 月提交量 29 亿次之后:GitHub 的危机、Azure 迁移,以及 .NET 的机会 从 PostgreSQL 到 Kubernetes:开源的护城河,从来不写在代码里 人工智能最先替代的,是人工智能学院自己
C# 内存安全性的重大演进:重新定义 unsafe 关键字
张善友 · 2026-06-01 · via 博客园 - 张善友

导读:.NET 产品经理 Richard Lander 披露了 C# 16 与 .NET 11/12 的核心蓝图——在不丢失 GC 的前提下,让 C# 在底层内存控制上更接近 Rust 的安全保障。

TL;DR

一句话概括这场变革:把「指针」从洪水猛兽变成可控工具,把真正的危险精确到「解引用」这一刀。

核心数据:

  • .NET 11 预览版 → .NET 12 生产就绪
  • 新模型代号:MemorySafetyRules 1
  • 目标:让 C# 获得「近似 Rust」的内存安全,而非引入完整的借用检查器

一、问题的根源:旧的 unsafe 太粗糙

1.1 旧模型的「冤案」

在经典 C# 里,只要用了 int* 这类指针,就得套一层 unsafe 大括号。

问题是:这太过度了

  • 声明一个指针 int* p = &value; —— 编译器直接红牌
  • 但调个 Marshal.AllocHGlobal() —— 居然是「安全」的?

这种「内外不一致」让代码审计变成了真·扫雷游戏

表面风平浪静,内部暗流涌动。

1.2 新模型的「翻案」

新规则把「非安全」的判定从「用指针」改成「解引用非托管内存」。

操作 旧模型 新模型
int* p = &value; ❌ 必须 unsafe ✅ 安全
fixed (buf) 获取栈数组 ❌ 必须 unsafe ✅ 安全
*p = 42; 解引用 ✅ 可以安全 ❌ 必须 unsafe
stackalloc Span<T> 未初始化 ❌ 被「误杀」 ✅ 精准识别

二、stackalloc 的三条件:真危险的精确定位

这是新模型最核心的技术突破。

只有同时满足以下三条,stackalloc 才被判为 unsafe:

  1. 隐式转 Span:被转换成 Span<T>ReadOnlySpan<T>
  2. 无初始化列表:没用 stackalloc int buf[4] = {1,2,3,4}
  3. 在 SkipLocalsInitAttribute 成员内:内存暴露未初始化垃圾数据

一句话:精准锁定物理危险区域,不伤及无辜。


三、LDM 核心决策(2026年5月13日)

语言设计组的最新拍板:

3.1 safe 关键字

不用 SafeRuntime 那种元数据属性,直接引入 safe 上下文关键字

safe {
    // 这里面的代码经过编译器安全审计
}

3.2 类型声明上的 unsafe

直接标为编译期错误

unsafe class MyClass { }  // ❌ 废弃!

正确姿势:在具体成员上标记

3.3 字段的 unsafe

字段可以单独标记 unsafe

unsafe struct Buffer {
    unsafe byte* Data;  // ✅ 读取时需要 unsafe 上下文
}

3.4 签名与实现的解耦

这是最重磅的设计变更:

旧模型 新模型
方法签名标 unsafe → 方法体全程 unsafe 签名 unsafe = 仅外部契约 ✓
无法区分「对外承诺」vs「内部实现」 内部仍受编译器安全保护 ✅
// 新模型下:
unsafe void ProcessBuffer(byte* ptr) {
    // 签名不安全?但方法体内部可以是安全的!
    
    // 只有真的解引用时才需要 unsafe 块
    unsafe {
        *ptr = 42;
    }
}

3.5 过渡期诊断

为了避免「升级空窗期」,编译器很贴心:

  • 没开启新模型的代码调用新版指针成员?
  • 调用方不在 unsafe 上下文?

编译警告/错误,给你慢慢迁移的时间。


四、ref 安全性的「逃生通道」

ref 安全性分析很保守,经常误杀「实则安全」的代码。

新模型在 unsafe 上下文中做了降级处理

原先 新模型
硬性编译错误 ⚠️ 警告
无法绕过 需要三层确认:
  1. 开启 /unsafe
  2. 声明 unsafe 上下文
  3. 显式压制警告

这就是「打破玻璃」的合法通道。


五、C# vs Rust:Runtime 的根本分歧

         .NET 垃圾回收器
    (自动且不确定性地管理内存)
              ↑
     [高性能路径]
              ↓
+------------------+    +------------------+
| 次级引用下行借用 |    | ArrayPool 复用  |
| (ref struct /   |    | (无仿射所有权,  |
|  生命周期单向栈传) |    | 面临二次释放隐患)|
+------------------+    +------------------+

Rust 靠的是所有权+生命周期+借用检查器 → 完全静态保障

C# 必须走另一条路:次级引用(Second-class References)

「只能往下传,不能存堆上」—— 低追踪成本实现高强度安全

但这也暴露了短板:ArrayPool 的归还无法阻止二次访问


六、生态迁移:三张表

6.1 P/Invoke 迁移

旧模式 新模式
IntPtr 伪装指针,无 unsafe 原生方法导入标记 unsafe 契约 ✅

动作IntPtr/nintbyte*/void* 重构

6.2 数组/栈缓冲区

旧模式 新模式
全局 unsafe 方法体 局部 unsafe { } 块隔离 ✅

替代方案:优先使用 Span<T> 或 C# 12 内联数组

6.3 显式布局联合体

旧模式 新模式
隐式重叠,缺乏审计 必须显式标注 safe/unsafe ✅

七、架构设计建议

三条落地准则:

① 互操作隔离层

  • 所有 P/Invoke、非托管封装 → 独立底层程序集
  • 开启 <MemorySafetyRules>1</MemorySafetyRules> 强制内部升级

② 局部化 unsafe

  • 丢弃「整方法 unsafe」的惯性
  • 只在最关键的解引用那几行unsafe { }

配合文档标签:

/// <summary>
/// 调用前必须确保 ptr 指向有效且已初始化的内存
/// </summary>
unsafe byte* ProcessBuffer(byte* ptr);

③ 优先托管替代

能用 Span<T> 就别用裸指针。

JIT 编译器对 Span 的边界检查消除已足够成熟,性能不输裸指针。


结语

这场演进的核心不是「让 C# 变成 Rust」,而是让内存安全的边界从「暴力圈地」变成「精准灌溉」

  • 把真正的危险留给开发者自己决定
  • 把「无害声明」从不必要的管控中解放出来
  • 给足「逃生通道」,但要求你清醒

.NET 11 预览在望,.NET 12 生产就绪。

你的 unsafe 代码,准备好迎接新十年了吗?


参考资料:C# Language Design Minutes - May 2026