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

上面的例子中我们将 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# 泛型的表达能力,使得我们可以更好地利用参数化多态来更高效地对代码进行抽象 。
此前当遇到字符串时,如果我们想要编写一个方法来对字符串进行解析,得到我们想要的类型的话,要么需要针对各种重载都编写一份,要么写成泛型方法 , 然后再在里面判断类型 。两种方法编写起来都非常的麻烦:
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);// ...}尽管 JIT 有能力在编译时消除掉多余的分支(因为 T 在编译时已知),编写起来仍然非常费劲,并且无法处理没有覆盖到的情况 。
但现在我们只需要利用接口的虚静态方法,即可高效的对所有实现了 IParsable<T> 的类型实现这个 Parse 方法 。.NET 标准库中已经内置了不少相关类型,例如 System.IParsable<T> 的定义如下:
public interface IParsable<TSelf> where TSelf : IParsable<TSelf>?{abstract static TSelf Parse(string s, IFormatProvider? provider);abstract static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result);}那么,我们只需要编写一个:
T Parse<T>(string str) where T : IParsable<T>{return T.Parse(str, null);}即可 。
这样,哪怕是其他地方定义的类型,只要实现了 IParsable<T>,就能够传到这个方法中:
struct Point : IParsable<Point>{public int X, Y;public static Point Parse(string s, IFormatProvider? provider) { ... }public static bool TryParse(string? s, IFormatProvider? provider, out Point result) { ... }}当然,既然是虚静态方法,那就意味着不仅仅可以是 abstract,更可以是 virtual 的,如此一来我们还可以提供自己的默认实现:
interface IFoo{virtual static void Hello() => Console.WriteLine("hello");}DisposeIDisposable我们有时需要显式地手动控制资源释放,而不是一味地交给 GC 来进行处理,那么此时我们的老朋友 Dispose 就派上用场了 。
对于 classstructrecord 而言,我们需要为其实现 IDisposable 接口,而对于 ref struct 而言,我们只需要暴露一个 public void Dispose() 。这样一来,我们便可以用 using 来自动进行资源释放 。
例如:
// 在 foo 的作用域结束时自动调用 foo.Dispose()using Foo foo = new Foo();// ...// 显式指定 foo 的作用域using (Foo foo = new Foo()){// ...}struct Foo : IDisposable{private void* memory;private bool disposed;public void Dispose(){if (disposed) return;disposed = true;NativeMemory.Free(memory);}}异常处理的编译优化异常是个好东西,但是也会对效率造成影响 。因为异常在代码中通常是不常见的 , 因为 JIT 在编译代码时,会将包含抛出异常的代码认定为冷块(即不会被怎么执行的代码块),这么一来会影响 inline 的决策:
void Foo(){// ...throw new Exception();}例如上面这个 Foo 方法 , 就很难被 inline 掉 。
但是,我们可以将异常拿走放到单独的方法中抛出,这么一来 , 抛异常的行为就被我们转换成了普通的函数调用行为 , 于是就不会影响对 Foo 的 inline 优化,将冷块从 Foo 转移到了 Throw 中:
[DoesNotReturn] void Throw() => throw new Exception();void Foo(){// ...Throw();}考虑到目前 .NET 还没有 bottom types 和 union types,当我们的 Foo 需要返回东西的时候,很显然上面的代码会因为不是所有路径都返回了东西而报错,此时我们只需要将 Throw 的返回值类型改成我们想返回的类型 , 或者干脆封装成泛型方法然后传入类型参数即可 。因为

推荐阅读