原文:https://blogs.msdn.microsoft.com/mazhou/2018/03/02/c-7-series-part-9-ref-structs/ 背景 在之前的文章中,我解釋了許多新的C#特性,每一個特性都是為了增強語言或者解決問題而引入的。具體來說,我解釋了值類型和引用類型 ...
原文:https://blogs.msdn.microsoft.com/mazhou/2018/03/02/c-7-series-part-9-ref-structs/
背景
在之前的文章中,我解釋了許多新的C#特性,每一個特性都是為了增強語言或者解決問題而引入的。具體來說,我解釋了值類型和引用類型、按值傳遞參數、按引用傳遞參數、ref局部變數和ref返回結果以及in參數。這其中許多功能是為高性能場景設計的。
ref和in參數可以幫助避免複製值,從而減少記憶體分配。當你有分配在堆棧的局部變數作為方法的實際參數傳遞時,這麼做是有效率的的,在這種情況下,所有的分配都在堆棧上;不需要堆分配。
對於高性能和原生開發場景,你可能希望“僅限堆棧”類型始終停留在執行堆棧上,因此對這種類型的對象的操作只能發生在堆棧上,在作用域中公開給托管堆的任何對這種類型的外部引用都應該被禁止。
ref結構
ref struct是僅在堆棧上的值類型:
- 表現一個順序結構的佈局;(譯註:可以理解為連續記憶體)
- 只能在堆棧上使用。即用作方法參數和局部變數;
- 不能是類或正常結構的靜態或實例成員;
- 不能是非同步方法或lambda表達式的方法參數;
- 不能動態綁定、裝箱、拆箱、包裝或轉換。
ref struct也被稱為嵌入式引用。
示例
下麵的代碼定義了一個ref結構。
public ref struct MyRefStruct { public int MyIntValue1; public int MyIntValue2; [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object obj) => throw new NotSupportedException(); [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() => throw new NotSupportedException(); [EditorBrowsable(EditorBrowsableState.Never)] public override string ToString() => throw new NotSupportedException(); }
請註意,我已經覆蓋了從System.Object繼承的Equals、GetHashCode和ToString方法。因為ref結構不允許裝箱,所以你將無法調用這兩個(譯註:原文兩個,應該是三個)基方法。
你可以在方法參數或局部變數中使用MyRefStruct作為常規值類型,但不能在其他地方使用它。
你也可以創建只讀ref結構,只需將readonly指令添加到ref結構聲明中即可。
public readonly ref struct MyRefStruct { public readonly int MyIntValue1; public readonly int MyIntValue2; public MyRefStruct(int value1, int value2) { this.MyIntValue1 = value1; this.MyIntValue2 = value2; } [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object obj) => throw new NotSupportedException(); [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() => throw new NotSupportedException(); [EditorBrowsable(EditorBrowsableState.Never)] public override string ToString() => throw new NotSupportedException(); }
與常規只讀結構一樣,需要將所有實例欄位/屬性設置為只讀。
元數據
ref結構在C# 7.2中可用。此功能需要編譯器級別更改才能工作,以便與以前編譯器生成的程式集向後相容。編譯器會為ref結構聲明發出[Obsolete]和[IsByRefLike]特性。
如果任何舊的程式集引用了包含ref結構類型的庫,[Obsolete]屬性將影響並阻止代碼編譯。
下麵是為上面的ref結構聲明生成的IL。
.class public sequential ansi sealed beforefieldinit Demo.MyRefStruct extends [System.Runtime]System.ValueType { .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [System.Runtime]System.ObsoleteAttribute::.ctor(string, bool) = ( 01 00 52 54 79 70 65 73 20 77 69 74 68 20 65 6d 62 65 64 64 65 64 20 72 65 66 65 72 65 6e 63 65 73 20 61 72 65 20 6e 6f 74 20 73 75 70 70 6f 72 74 65 64 20 69 6e 20 74 68 69 73 20 76 65 72 73 69 6f 6e 20 6f 66 20 79 6f 75 72 20 63 6f 6d 70 69 6c 65 72 2e 01 00 00 ) .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 ) // Fields .field public initonly int32 MyIntValue1 .field public initonly int32 MyIntValue2 // Methods .method public hidebysig specialname rtspecialname instance void .ctor ( int32 value1, int32 value2 ) cil managed { // Method begins at RVA 0x2090 // Code size 16 (0x10) .maxstack 8 // (no C# code) IL_0000: nop // this.MyIntValue1 = value1; IL_0001: ldarg.0 IL_0002: ldarg.1 IL_0003: stfld int32 Demo.MyRefStruct::MyIntValue1 // this.MyIntValue2 = value2; IL_0008: ldarg.0 IL_0009: ldarg.2 IL_000a: stfld int32 Demo.MyRefStruct::MyIntValue2 // (no C# code) IL_000f: ret } // end of method MyRefStruct::.ctor .method public hidebysig virtual instance bool Equals ( object obj ) cil managed { // Method begins at RVA 0x20a1 // Code size 6 (0x6) .maxstack 8 // throw new NotSupportedException(); IL_0000: newobj instance void [System.Runtime]System.NotSupportedException::.ctor() // (no C# code) IL_0005: throw } // end of method MyRefStruct::Equals .method public hidebysig virtual instance int32 GetHashCode () cil managed { .custom instance void [System.Runtime]System.ComponentModel.EditorBrowsableAttribute::.ctor(valuetype [System.Runtime]System.ComponentModel.EditorBrowsableState) = ( 01 00 01 00 00 00 00 00 ) // Method begins at RVA 0x20a8 // Code size 6 (0x6) .maxstack 8 // throw new NotSupportedException(); IL_0000: newobj instance void [System.Runtime]System.NotSupportedException::.ctor() // (no C# code) IL_0005: throw } // end of method MyRefStruct::GetHashCode .method public hidebysig virtual instance string ToString () cil managed { .custom instance void [System.Runtime]System.ComponentModel.EditorBrowsableAttribute::.ctor(valuetype [System.Runtime]System.ComponentModel.EditorBrowsableState) = ( 01 00 01 00 00 00 00 00 ) // Method begins at RVA 0x20af // Code size 6 (0x6) .maxstack 8 // throw new NotSupportedException(); IL_0000: newobj instance void [System.Runtime]System.NotSupportedException::.ctor() // (no C# code) IL_0005: throw } // end of method MyRefStruct::ToString } // end of class Demo.MyRefStruct
Span<T>和Memory<T>
有了類ref類型的支持,現在就可以為所有連續記憶體訪問提供統一的類型。System.Span<T>表示記憶體的連續空間,可用於執行堆棧、托管堆和非托管堆的通用記憶體操作。
下麵是ReadOnlySpan<T>的一個簡單用法,用於去掉字元串的開始的空格。
internal class Program { private static void Main(string[] args) { string text = " I am using C# 7.2 Span<T>!"; Console.WriteLine(TrimStart(text).ToArray()); } private static ReadOnlySpan<char> TrimStart(ReadOnlySpan<char> text) { if (text.IsEmpty) { return text; } int i = 0; char c; while ((c = text[i]) == ' ') { i++; } return text.Slice(i); } }
結論
C# 7.2為高性能場景添加了語言特性,併為低級別的原生開發和互操作性場景提供了效率。ref結構還可以與stackalloc、Span<T>、fixed buffers和Ranges(C# 7.3)一起用於生產力。
註意:要使用這個特性,需要Visual Studio 2017 15.5或更高版本。
系列文章:
- [譯]C# 7系列,Part 1: Value Tuples 值元組
- [譯]C# 7系列,Part 2: Async Main 非同步Main方法
- [譯]C# 7系列,Part 3: Default Literals 預設文本表達式
- [譯]C# 7系列,Part 4: Discards 棄元
- [譯]C# 7系列,Part 5: private protected 訪問修飾符
- [譯]C# 7系列,Part 6: Read-only structs 只讀結構
- [譯]C# 7系列,Part 7: ref Returns ref返回結果
- [譯]C# 7系列,Part 8: in Parameters in參數