当前位置: 移动技术网 > IT编程>开发语言>c# > [译]C# 7系列,Part 8: in Parameters in参数

[译]C# 7系列,Part 8: in Parameters in参数

2019年12月21日  | 移动技术网IT编程  | 我要评论
原文: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特性一起使用,比如引用返回和引用结构。

 

系列文章:

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网