Unsafe.As 是零开销的内存重解释工具,仅在 sizeof(TFrom) == sizeof(TTo) 时允许编译,不进行类型检查、构造、装箱/拆箱,失败导致未定义行为;不是 as 关键字的 unsafe 版本,也不适用于引用类型或大小不同的类型转换。
Unsafe.As 不是类型转换操作符,也不是 as 关键字的“unsafe 版本”。它是一个零开销的**内存重解释(bitwise reinterpret)** 工具,作用是告诉编译器:“请把这块内存当作 TTo 类型来读,不管它原本是什么类型”——不检查兼容性、不调用构造函数、不触发装箱/拆箱,甚至不关心 TFrom 和 TTo 是否有继承或转换关系。
TFrom 和 TTo 占用相同大小(sizeof(TFrom) ==
sizeof(TTo))时才被允许编译,否则报错null;出错直接导致未定义行为(比如读到垃圾值、崩溃、数据错乱)Unsafe.As 是非法的,因为引用类型大小受 GC 影响且语义不可重解释)常规类型转换(as、is、(T)obj)本质是运行时类型系统参与的**安全检查 + 可能的引用调整**;而 Unsafe.As 完全绕过整个类型系统,只是指针偏移 + 位宽断言。所以它的“性能优势”不是“快一点”,而是“没有额外开销”:
obj as string:检查 obj 是否为 null 或是否实际是 string 实例(一次虚表/类型句柄查表)(string)obj:同上检查,失败则抛 InvalidCastException
Unsafe.As(ref i) :编译期确认 sizeof(int) == sizeof(float),生成一条 mov 指令(或零指令),无分支、无异常路径、无 GC 堆校验但注意:这种“零成本”只在你**100% 确保内存布局一致且生命周期可控**时成立。一旦用错,性能再高也没意义——程序已经不可靠了。
它不是为了替代 as,而是为极少数需要跨类型按位复用内存的底层场景服务,比如:
Span 的前 4 字节快速解释为 int:Spanbytes = stackalloc byte[4] { 0x01, 0x00, 0x00, 0x00 }; int value = Unsafe.As (ref bytes.DangerousGetPinnableReference());
ReadOnlySpan 和 ReadOnlySpan 之间做 SIMD 向量对齐转换(配合 Vector)long 时间戳当两个 int 拆开处理)⚠️ 这些场景共同点:你知道原始数据来源、长度固定、对齐明确、且全程控制内存生命周期(比如栈分配、pinning 后的数组、或 Span 背后的托管数组已确保不会移动)。
这是最危险的误解。下面这些写法全是错的,而且错误非常隐蔽:
object o = "hello"; string s = Unsafe.As → 编译失败(引用类型不支持)int i = 123; long l = Unsafe.As(ref i); → 编译失败(sizeof(int) != sizeof(long))var list = new List(); int* ptr = Unsafe.As, int*>(ref list);
→ 即便编译通过,解引用 ptr 会读取对象头+字段,结果完全不可预测Unsafe.As 得到的引用 → 可能因 GC 移动原对象而悬空真正需要 Unsafe.As 的地方极少;绝大多数所谓“性能瓶颈”,其实是算法、缓存局部性或内存分配问题,而不是 as 多花了几个纳秒。把它当成类型转换的“加速版”,基本等于给自行车加涡轮增压——装上了,但路不对,跑不远。