zoukankan      html  css  js  c++  java
  • [译]C# 7系列,Part 8: in Parameters in参数

    原文: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 = value + 1;
        return value;
    }

    若要创建只读引用参数,请在参数前增加in修饰符。

    public int Increment(in int value)
    {
        //不能重新赋值,变量value是只读的。
        int returnValue = 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特性一起使用,比如引用返回和引用结构。

    系列文章:

  • 相关阅读:
    Python中如何调用Linux命令
    python入门小记
    DNS域名解析的过程
    阿里云Aliyun_server
    ipython及Python初体验
    js 的数值限制可能引起的问题
    页面图片中间有条线----解决
    ie6幽灵文字及解决办法
    解除工商银行15分钟限制
    ie6 无法显示网页 已终止操作
  • 原文地址:https://www.cnblogs.com/childking/p/12784279.html
Copyright © 2011-2022 走看看