如何获取ReadOnlySpan这类ref struct的托管内存大小?
你提到的问题很典型——ref struct(比如ReadOnlySpan<byte>)因为其特殊的设计(仅能在栈上分配、无法被装箱),常规的内存大小检查方法都会失效,我们来一步步拆解原因并给出可行方案:
为什么常规方法行不通?
你尝试的几种方法都因为ref struct的特性被编译器阻止:
unsafe { size = sizeof(ReadOnlySpan<byte>); }报CS0208:编译器禁止对ref struct直接使用sizeof,这类类型虽属值类型,但受限于栈分配约束,不允许在unsafe块中直接获取其大小。Marshal.SizeOf(default(ReadOnlySpan<byte>))和Marshal.SizeOf(typeof(ReadOnlySpan<byte>))报错:Marshal.SizeOf仅适用于可 blittable 的非托管类型或能转换为非托管类型的类型,而ref struct无法被装箱或转换为非托管类型,自然不支持。
可行的解决方案:使用Unsafe.SizeOf<T>()
最简单可靠的方法是使用System.Runtime.CompilerServices.Unsafe类中的SizeOf<T>()方法,它可以直接获取任意值类型(包括ref struct)的内存大小——原理是直接读取类型的元数据布局信息,不需要实例,也不受装箱或托管类型限制。
示例代码:
using System.Runtime.CompilerServices; // 获取ReadOnlySpan<byte>的内存大小 int spanSize = Unsafe.SizeOf<ReadOnlySpan<byte>>(); // 64位系统下返回16(两个8字节的IntPtr字段),32位系统下返回8(两个4字节的IntPtr字段)
注意:不要手动计算
ref struct的大小(比如假设ReadOnlySpan是两个IntPtr字段),因为类型的内部布局可能随.NET版本或平台变化,Unsafe.SizeOf<T>()会自动读取官方元数据,是最可靠的方式。
结合微软的in修饰符建议
回到你提到的微软《编写安全高效的C#代码》中的建议:对大小超过System.IntPtr.Size的readonly struct参数使用in修饰符。对于ReadOnlySpan<byte>这类ref struct,它的大小在64位系统下是16字节(超过8字节的IntPtr.Size),所以如果它作为参数传递时,使用in修饰符可以避免不必要的栈拷贝,进一步提升性能——不过要注意,ref struct本身已经是栈分配的,但in修饰符会确保参数是只读引用传递,优化效果更显著。
如果是你自定义的readonly ref struct,也可以用同样的方法检查大小,来决定是否使用in修饰符。
内容的提问来源于stack exchange,提问作者Joel V. Earnest-DeYoung

