.NET 零開銷抽象指南( 四 )

上面的例子中我們將 X、YXY 的內存重疊 , 并且利用 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 就派上用場了 。
對于 class、structrecord 而言,我們需要為其實現 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 的返回值類型改成我們想返回的類型 , 或者干脆封裝成泛型方法然后傳入類型參數即可 。因為

推薦閱讀