[译]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特性一起使用,比如引用返回和引用结构。

系列文章:

原文地址:https://www.cnblogs.com/wenhx/p/csharp-7-series-part-8-in-parameters.html

时间: 2024-11-08 22:46:55

[译]C# 7系列,Part 8: in Parameters in参数的相关文章

[译]C# 7系列,Part 9: ref structs ref结构

原文:https://blogs.msdn.microsoft.com/mazhou/2018/03/02/c-7-series-part-9-ref-structs/ 背景 在之前的文章中,我解释了许多新的C#特性,每一个特性都是为了增强语言或者解决问题而引入的.具体来说,我解释了值类型和引用类型.按值传递参数.按引用传递参数.ref局部变量和ref返回结果以及in参数.这其中许多功能是为高性能场景设计的. ref和in参数可以帮助避免复制值,从而减少内存分配.当你有分配在堆栈的局部变量作为方

[译]C# 7系列,Part 2: Async Main

原文:https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main/ 你大概知道,C#语言可以构建两种程序.一种是带有入口点(entrypoint)的程序,这样操作系统就可以加载程序并从入口点执行;另一个是没有入口点的程序.操作系统不能直接执行程序,程序可以被其他有入口点的程序引用,这样就可以执行其中的代码. 必须有入口点的应用程序类型有:Windows Forms应用程序.UWP应用程序.控制台应

[译]C# 7系列,Part 5: private protected 访问修饰符

原文:https://blogs.msdn.microsoft.com/mazhou/2017/10/05/c-7-series-part-5-private-protected/ C#有几个可访问性修饰符,public.internal.internal protected和private. public: 具有此可访问性声明的成员可以在此成员所在程序集中或引用该成员所在程序集的任何其他程序集中可见.也就是说,访问不受限制. internal:具有此可访问性声明的成员可以在此成员所在程序集中可

[译]C# 7系列,Part 7: ref Returns ref返回结果

原文:https://blogs.msdn.microsoft.com/mazhou/2017/12/12/c-7-series-part-7-ref-returns/ 背景 有两种方法可以将一个值传递给一个方法: 按值传递.当一个参数被传递给一个方法时,一个参数的副本(如果它是一个值类型)或一个"参数引用"的副本(如果它是一个引用类型)被传递.当您更改方法中的参数时,更改(单个赋值或复合赋值)会反映到参数/"参数引用"的副本,而不会反映到参数或“参数引用”本身.这

Tokyo Tyrant(TTServer)系列(二)-启动参数和配置

启动参数介绍 ttserver命令可以启动一个数据库实例.因为数据库已经实现了Tokyo Cabinet的抽象API,所以可以在启动的时候指定数据库的配置类型. 支持的数据库类型有: 内存hash数据库 内存tree数据库 hash数据库 B+ tree数据库, 命令通过下面的格式来使用,'dbname'制定数据库名,如果省略,则被视作内存hash数据库. ttserver [-host name] [-port num] [-thnum num] [-tout num] [-dmn] [-pi

Parameters.AddWithValue(“@参数”,value)方法

以前用command方法执行存储过程增加参数时,总是先用cmd.Parameters.Add方法来设置参数和参数类型,再用Parameters[0].Value来给参数赋值.以前的一个动作代码示例: 1 string strConn = "Data Source=.;Initial Catalog=HISDB;Integrated Security=True"; 2 3 using( SqlConnection conn = new SqlConnection(strConn)) 4

[译]GC专家系列2:Java 垃圾回收的监控

原文链接:http://www.cubrid.org/blog/dev-platform/how-to-monitor-java-garbage-collection/ 这是"成为GC专家系列"文章的第二篇.在第一篇理解Java垃圾回收中我们学习了几种不同的GC算法的处理过程,GC的工作方式,新生代与老年代的区别.到目前为止,你应该已经了解了JDK 7中的5种GC类型,以及每种GC对性能的影响. 在本篇中,我将介绍JVM在真实环境中如何运行GC的. 什么是GC监控 GC监控 指的是在运

[译]C# 7系列,Part 1: Value Tuples

Mark Zhou写了很不错的一系列介绍C# 7的文章,虽然是2年多年前发布的,不过对于不熟悉C# 7特性的同学来说,仍然有很高的阅读价值. 原文:https://blogs.msdn.microsoft.com/mazhou/2017/05/26/c-7-series-part-1-value-tuples/ 译文: 从今天开始,我将开始一个新的C# 7系列文章,介绍C# 7+的新语言特性.请注意,我说的不是C# 7.0,我说的是C# 7+,因为将会有一些小的版本(比如7.1.7.2)逐步引入

【译】STM32L4x6系列用户手册第四章 - 防火墙(FireWall)

4        防火墙(FW) 4.1        简介 防火墙用于保护非易失性存储器中的特定部分的代码或数据,和/或保护SRAM1中的易失性数据,免受在保护区域外部执行的其余代码的非法访问. 4.2        防火墙的主要功能 •        防火墙保护的代码(代码段)可能位于: –        Flash存储器 –        SRAM1存储器,如果在防火墙配置步骤中被声明为可执行保护区域. •        要保护的数据也可以位于 –        Flash存储器中(非易失