.NET 零开销抽象指南( 四 )

Unsafe.*Marshal.*MemoryMarshal.*CollectionsMarshal.*NativeMemory.*Buffer.* 中 。利用这些工具函数,我们可以非常高效地在几乎不直接使用指针的情况下,操作各类内存、引用和数组、集合等等 。当然,使用的前提是你有相关的知识并且明确知道你在干什么,不然很容易写出不安全的代码,毕竟这里面大多数 API 就是 unsafe 的 。
例如消除掉边界检查的访问:
void Foo(Span<int> s){Console.WriteLine(Unsafe.Add(ref MemoryMarshal.GetReference(s), 3));}Span<int> s = new[] { 1, 2, 3, 4, 5, 6 };Foo(s); // 4查看生成的代码验证:
G_M000_IG02:;; offset=0004Hmovrcx, bword ptr [rcx]movecx, dword ptr [rcx+0CH]call[System.Console:WriteLine(int)]可以看到,边界检查确实被消灭了,对比直接访问的情况:
void Foo(Span<int> s){Console.WriteLine(s[3]);}G_M000_IG02:;; offset=0004Hcmpdword ptr [rcx+08H], 3 ; <-- range checkjbeSHORT G_M000_IG04movrcx, bword ptr [rcx]movecx, dword ptr [rcx+0CH]call[System.Console:WriteLine(int)]nopG_M000_IG04:;; offset=001CHcallCORINFO_HELP_RNGCHKFAILint3再比如,直接获取字典中成员的引用:
Dictionary<int, int> dict = new(){[1] = 7,[2] = 42};// 如果存在则获取引用,否则添加一个 default 进去然后再返回引用ref int value = https://www.huyubaike.com/biancheng/ref CollectionsMarshal.GetValueRefOrAddDefault(dict, 3, out bool exists);value++;Console.WriteLine(exists); // falseConsole.WriteLine(dict[3]); // 1如此一来,我们便不需要先调用 ContainsKey 再操作,只需要一次查找即可完成我们需要的操作,而不是 ContainsKey 查找一次,后续操作再查找一次 。
我们还可以用 Buffer.CopyMemory 来实现与 memcpy 等价的高效率数组拷贝;再有就是前文中出现过的 NativeMemory,借助此 API,我们可以手动分配非托管内存,并指定对齐方式、是否清零等参数 。
显式布局、字段重叠和定长数组C# 的 struct 允许我们利用 [StructLayout] 按字节手动指定内存布局,例如:
unsafe{Console.WriteLine(sizeof(Foo)); // 10}[StructLayout(LayoutKind.Explicit, Pack = 1)]struct Foo{[FieldOffset(0)] public int X;[FieldOffset(4)] public float Y;[FieldOffset(0)] public long XY;[FieldOffset(8)] public byte Z;[FieldOffset(9)] public byte W;}上面的例子中我们将 XYXY 的内存重叠,并且利用 Pack 指定了 padding 行为,使得 Foo 的长度为 10 字节,而不是 12 字节 。
我们还有定长数组:
Foo foo = new Foo();foo.Color[1] = 42;struct Foo{public unsafe fixed int Array[4];}此时,我们便有一个长度固定为 4 的数组存在于 Foo 的字段中,占据 16 个字节的长度 。
接口的虚静态方法.NET 7 中我们迎来了接口的虚静态方法,这一特性加强了 C# 泛型的表达能力,使得我们可以更好地利用参数化多态来更高效地对代码进行抽象 。
此前当遇到字符串时,如果我们想要编写一个方法来对字符串进行解析,得到我们想要的类型的话,要么需要针对各种重载都编写一份,要么写成泛型方法,然后再在里面判断类型 。两种方法编写起来都非常的麻烦:
【.NET 零开销抽象指南】int Parse(string str);long Parse(string str);float Parse(string str);// ...或者:
T Parse<T>(string str){if (typeof(T) == typeof(int)) return int.Parse(str);if (typeof(T) == typeof(long)) return long.Parse(str);if (typeof(T) == typeof(float)) return float.Parse(str);// ...}

经验总结扩展阅读