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

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

public int Increment(in int value)
{
//不能重新赋值,变量value是只读的。
int returnValue = value + ;
return returnValue;
}

如果重新赋值,编译器将生成一个错误。

可以使用常规方法来调用这个方法。

int v = ;
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(); // A(int)
int x = ;
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
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 []
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( )
// Code size 13 (0xd)
.maxstack
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
.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 []
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( )
.param []
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( )
.param []
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( )
.param []
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( ) }

正如你所看到的,编译器为每个in参数产生了使用[in]指令、引用数据类型和[IsReadOnly]特性的代码。由于参数已从传值更改为传引用, P/Invoke可能会由于原始签名不匹配而失败。

结论

in参数是扩展C#语言的一个很棒的特性,它易于使用,并且是二进制兼容的(不需要对CLR进行更改)。只读引用参数在修改只读参数时给出编译时错误,有助于避免错误。这个特性可以与其他ref特性一起使用,比如引用返回和引用结构。

系列文章:

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

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

    原文:https://blogs.msdn.microsoft.com/mazhou/2018/03/02/c-7-series-part-9-ref-structs/ 背景 在之前的文章中,我解释了 ...

  2. [译]C# 7系列,Part 1: Value Tuples 值元组

    Mark Zhou写了很不错的一系列介绍C# 7的文章,虽然是2年多年前发布的,不过对于不熟悉C# 7特性的同学来说,仍然有很高的阅读价值. 原文:https://blogs.msdn.microso ...

  3. [译]C# 7系列,Part 2: Async Main 异步Main方法

    原文:https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main/ 你大概知道,C#语言可以构建两种 ...

  4. [译]C# 7系列,Part 3: Default Literals 默认文本表达式

    原文:https://blogs.msdn.microsoft.com/mazhou/2017/06/06/c-7-series-part-3-default-literals/ C#的default ...

  5. [译]C# 7系列,Part 4: Discards 弃元

    原文:https://blogs.msdn.microsoft.com/mazhou/2017/06/27/c-7-series-part-4-discards/ 有时我们想要忽略一个方法返回的值,特 ...

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

    原文:https://blogs.msdn.microsoft.com/mazhou/2017/10/05/c-7-series-part-5-private-protected/ C#有几个可访问性 ...

  7. [译]C# 7系列,Part 6: Read-only structs 只读结构

    原文:https://blogs.msdn.microsoft.com/mazhou/2017/11/21/c-7-series-part-6-read-only-structs/ 背景 在.NET世 ...

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

    原文:https://blogs.msdn.microsoft.com/mazhou/2017/12/12/c-7-series-part-7-ref-returns/ 背景 有两种方法可以将一个值传 ...

  9. [译]C# 7系列,Part 10: Span<T> and universal memory management Span<T>和统一内存管理

    原文:https://blogs.msdn.microsoft.com/mazhou/2018/03/25/c-7-series-part-10-spant-and-universal-memory- ...

随机推荐

  1. Swoft源码之Swoole和Swoft的分析

    这篇文章给大家分享的内容是关于Swoft 源码剖析之Swoole和Swoft的一些介绍(Task投递/定时任务篇),有一定的参考价值,有需要的朋友可以参考一下. 前言 Swoft的任务功能基于Swoo ...

  2. MySQL常用的查询语句回顾

    让你快速复习语句的笔记宝典. create table users(    username varchar(20) primary key,    userpwd varchar(20) ) alt ...

  3. react create-react-app使用less 及关闭eslint

    使用less和关闭eslint都需要先运行命令 npm run  eject 来暴露配置文件,(不可逆的) 一.less使用 运行命令安装less npm install less less-load ...

  4. 在idea中使用git

    在idea中使用git 1. 在idea中配置git ​ 安装好IntelliJ IDEA后,如果Git安装在默认路径下,那么idea会自动找到git的位置,如果更改了Git的安装位置则需要手动配置下 ...

  5. element 根据某一个属性合并列

    通过 span-method 绑定方法 objectSpanMethod方法 this.getSpanArr(this.tableData); //this.tableData 指接口取到的数据 // ...

  6. 程序员的算法课(20)-常用的图算法:最小生成树(MST)

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/m0_37609579/article/de ...

  7. 关于jsp页面的复选框(checkbox)取值的获取问题

    复选框的取值问题可以使用js和jQuery来获取: jQuery API : each(callback) :以每一个匹配的元素作为上下文来执行一个函数. :checked :匹配所有选中的被选中元素 ...

  8. 优雅解决 SpringBoot 工程中多环境下 application.properties 的维护问题

    微信号:geekoftaste, 期待与大家一起探讨! 背景 我们知道 SpringBoot 有一个全局的配置文件 application.properties, 可以把工程里用到的占位符,第三方库的 ...

  9. CSS+HTML实现移动端div左右滑动展示

    由于手机屏幕的宽度有限,内容太多移动设备一行装不下的,所以很多移动端网站的导航栏都有左右滑动效果,下面我就用CSS+HTML实现移动端div左右滑动展示. CSS:box设置文本不换行,子元素box1 ...

  10. nginx、redis在Centos7中,加入开机自启动

    nginx加入开机启动 cat <<EOF >> /etc/systemd/system/nginx.service> [Unit]> Description=ng ...