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

尽管 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 的返回值类型改成我们想返回的类型,或者干脆封装成泛型方法然后传入类型参数即可 。因为 throw 在 C# 中隐含了不会返回的含义,编译器遇到 throw 时知道这个是不会返回的,也就不会因为 Throw 没有返回东西而报错:
[DoesNotReturn] int Throw1() => throw new Exception();[DoesNotReturn] T Throw2<T>() => throw new Exception();int Foo1(){// ...return Throw1();}int Foo2(){// ...return Throw2<int>();}

经验总结扩展阅读