原文:https://blogs.msdn.microsoft.com/mazhou/2018/01/08/c-7-series-part-8-in-parameters/
背景
默認情況下,方法引數是通過值傳遞的,也就是說,引數被復制并傳遞到方法中,因此,修改方法體中的引數不會影響原始值,在大多數情況下,修改是不必要的,
其他編程語言,如C++,有一個const引數或類似的概念:這表明方法體中的引數是一個不能被重新賦值的常量,它有助于避免在方法體內無意中重新賦值一個方法引數的錯誤,并通過不允許不必要的賦值來提高性能,
C# 7.2引入了in引數(又名,只讀的參考引數,) 帶有in修飾符的方法引數意味著該引數是參考且在方法體中只讀,
in引數
讓我們以下面的方法定義為例,
public int Increment(int value) { //可以重新賦值,變數value是按值傳遞進來的, value = https://www.cnblogs.com/childking/p/value + 1; return value; }
若要創建只讀參考引數,請在引數前增加in修飾符,
public int Increment(in int value) { //不能重新賦值,變數value是只讀的, int returnValue = https://www.cnblogs.com/childking/p/value + 1; return returnValue; }
如果重新賦值,編譯器將生成一個錯誤,

可以使用常規方法來呼叫這個方法,
int v = 1; Console.WriteLine(Increment(v));
因為value變數是只讀的,所以不能將value變數放在等式左邊(即LValue),執行賦值的一元運算子也是不允許的,比如++或--,但是,你仍然可以獲取值的地址并使用指標操作進行修改,
解決多載
in是一個方法引數的修飾符,它表明此引數是參考型別,它被視為方法簽名的一部分,這意味著你可以有兩個方法多載,只是in修飾符不同,(譯注:一個有in,一個沒有in)
下面的代碼示例定義了兩個方法多載,只是參考型別不同,
public class C { public void A(int a) { Console.WriteLine("int a"); } public void A(in int a) { Console.WriteLine("in int a"); } }
默認情況下,方法呼叫將決議為值簽名的那個多載,為了清除歧義并顯式地呼叫參考簽名的多載,在顯式地呼叫A(in int)方法多載時,在實際的引數之前加上in修飾符,
private static void Main(string[] args) { C c = new C(); c.A(1); // A(int) int x = 1; c.A(in x); // A(in int) c.A(x); // A(int) }
程式輸出如下:

限制
因為in引數是只讀的參考引數,所以所有參考引數的限制都適用于in,
- 不能用于迭代器方法(即具有yield陳述句的方法),
- 不能用于async異步方法,
- 如果你用in修飾Main方法的args引數,則入口點的方法簽名會無效,
in引數和CLR
在.NET的CLR中已經有了一個類似的概念,所以in引數特性不需要改變CLR,
任何in引數在被編譯成MSIL時,都會在定義中附加一個[in]指令,為了觀察編譯行為,我使用ILDAsm.exe獲得上面示例反編譯的MSIL,
下面的MSIL代碼是方法C.A(int):
.method public hidebysig instance void A(int32 a) cil managed { // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "int a" IL_0006: call void [System.Console]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method C::A
下面的MSIL代碼是方法C.A(in int):
.method public hidebysig instance void A([in] int32& a) cil managed { .param [1] .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 ) // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "in int a" IL_0006: call void [System.Console]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method C::A
你看到區別了嗎?int32&顯示它是一個參考引數;[in]是一個指示CLR如何處理此引數的附加元資料,
下面的代碼是上面例子中Main方法的MSIL,它展示了如何呼叫這兩個C.A()方法的多載,
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 35 (0x23) .maxstack 2 .locals init (class Demo.C V_0, int32 V_1) IL_0000: nop IL_0001: newobj instance void Demo.C::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.1 IL_0009: callvirt instance void Demo.C::A(int32) IL_000e: nop IL_000f: ldc.i4.1 IL_0010: stloc.1 IL_0011: ldloc.0 IL_0012: ldloca.s V_1 IL_0014: callvirt instance void Demo.C::A(int32&) IL_0019: nop IL_001a: ldloc.0 IL_001b: ldloc.1 IL_001c: callvirt instance void Demo.C::A(int32) IL_0021: nop IL_0022: ret } // end of method Program::Main
在呼叫點,沒有其他元資料指示去呼叫C.A(in int),
in引數和互操作
在許多地方,[In]特性被用于與本機方法簽名匹配以實作互操作性,讓我們以下面的Windows API為例,
[DllImport("shell32")] public static extern int ShellAbout( [In] IntPtr handle, [In] string title, [In] string text, [In] IntPtr icon);
此方法對應的MSIL如下所示,
.method public hidebysig static pinvokeimpl("shell32" winapi) int32 ShellAbout([in] native int handle, [in] string title, [in] string text, [in] native int icon) cil managed preservesig
如果我們使用in修飾符來改變ShellAbout方法的簽名:
[DllImport("shell32")] public static extern int ShellAbout( in IntPtr handle, in string title, in string text, in IntPtr icon);
該方法生成的MSIL為:
.method public hidebysig static pinvokeimpl("shell32" winapi) int32 ShellAbout([in] native int& handle, [in] string& title, [in] string& cext, [in] native int& icon) cil managed preservesig { .param [1] .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 ) .param [2] .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 ) .param [3] .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 ) .param [4] .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 ) }
正如你所看到的,編譯器為每個in引數產生了使用[in]指令、參考資料型別和[IsReadOnly]特性的代碼,由于引數已從傳值更改為傳參考, P/Invoke可能會由于原始簽名不匹配而失敗,
結論
in引數是擴展C#語言的一個很棒的特性,它易于使用,并且是二進制兼容的(不需要對CLR進行更改),只讀參考引數在修改只讀引數時給出編譯時錯誤,有助于避免錯誤,這個特性可以與其他ref特性一起使用,比如參考回傳和參考結構,
系列文章:
- [譯]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引數 (本文)
- [譯]C# 7系列,Part 9: ref structs ref結構
- [譯]C# 7系列,Part 10: Span<T> and universal memory management Span<T>和統一記憶體管理 (完)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/47516.html
標籤:其他
上一篇:LiteByte教程
下一篇:KYLIN cube優化相關的
