为什么要在C#中使用"in"参数修饰符?

Tra*_*eed 54 c# c#-7.2

所以,我(我想)理解in参数修饰符的作用.但它的作用似乎是多余的.

通常,我认为使用a的唯一原因ref是修改调用变量,这是明确禁止的in.因此,通过in引用传递在逻辑上等同于传递值.

是否有某种性能优势?我认为,在事物的后端,ref参数必须至少复制变量的物理地址,该地址应与任何典型的对象引用相同.

那么,那么只是在更大的结构中的优势,还是有一些幕后编译器优化使其在其他地方具有吸引力?如果是后者,为什么我不应该把每个参数都做成in

dym*_*oid 57

in 最近被引入了C#语言.

in实际上是一个ref readonly.一般来说,只有一个用例in可以提供帮助:处理大量readonly structs的高性能应用程序.

假设你有:

readonly struct VeryLarge
{
    public readonly long Value1;   
    public readonly long Value2;

    public long Compute() { }
    // etc
}
Run Code Online (Sandbox Code Playgroud)

void Process(in VeryLarge value) { }
Run Code Online (Sandbox Code Playgroud)

在这种情况下,VeryLarge当在Process方法中使用此结构时(例如,在调用时value.Compute()),结构将通过引用传递而不创建防御性副本,并且编译器确保结构不变性.

请注意,struct使用in修饰符传递not-readonly 将导致编译器在调用struct的方法并访问上述方法中的属性时创建防御性副本Process,这将对性能产生负面影响!

有一个非常好的MSDN博客文章,我建议仔细阅读.

如果您想获得更多历史背景 - 介绍in,您可以在C#语言的GitHub存储库中阅读此讨论.

一般来说,大多数开发人员都认为引入in可能被视为一个错误.这是一种相当奇特的语言特征,只能用于高性能边缘情况.

  • @dymanoid:`in`和`out`都是从一开始就应该在框架中的概念(以及方法和属性可以指示它们是否修改了"this"的方法).编译器可以通过将引用传递给临时保存来允许将属性传递给`in`参数; 如果用另一种语言编写的函数修改了那个临时函数,那么语义就会有点诡异,但这就是函数行为与其签名不匹配的错误. (3认同)
  • @supercat,请不要在这里展开讨论(无论如何我并不在 .NET Framework 概念团队中)。答案中引用了一个很长的讨论,并且 GitHub 讨论引用了其他一些讨论 - 有趣的阅读。顺便说一句,您可以在 VB.NET 中通过引用传递属性 - 编译器会为您创建这些临时变量(当然这可能会导致一些模糊的问题)。 (3认同)

Eri*_*ert 23

通过in引用传递似乎在逻辑上等同于传递值.

正确.

是否有某种性能优势?

是.

我认为,在事物的后端,ref参数必须至少复制变量的物理地址,该地址应与任何典型的对象引用相同.

要求对对象的引用和对变量的引用都具有相同的大小,并且不要求任何一个是机器字的大小,但是是的,实际上两者都是32位32位机和64位机器上的64位.

你认为"物理地址"与它有什么关系我不清楚.在Windows上,我们使用虚拟地址,而不是用户模式代码中的物理地址.在什么情况下你会想象一个物理地址在C#程序中是有意义的,我很想知道.

也不要求将任何类型的引用实现为存储的虚拟地址.在CLI规范的一致实现中,引用可以是GC表中的不透明句柄.

只是在更大的结构中的优势?

降低传递较大结构的成本是该功能的激励方案.

请注意,无法保证in任何程序实际上更快,并且它可以使程序更慢. 所有关于表现的问题必须通过实证研究来回答.很少有优化总是胜利 ; 这不是"永远胜利"的优化.

是否有一些幕后编译器优化使其在其他地方具有吸引力?

如果这样做不违反C#规范的规则,则允许编译器和运行时进行任何优化.据我所知,还没有针对in参数的这种优化,但这并不排除将来的这种优化.

为什么我不应该把每个参数都放进去?

好吧,假设您创建了一个int参数而不是in int参数.是什么成本?

  • 呼叫站点现在需要变量而不是
  • 变量无法注册.抖动经过仔细调整的寄存器分配方案只是一把扳手.
  • 调用站点的代码更大,因为它必须对变量进行ref并将其放在堆栈上,而之前它只需将值推送到调用堆栈
  • 较大的代码意味着一些短跳转指令现在可能已成为长跳转指令,因此代码现在变大了.这会对各种事物产生连锁反应.高速缓存越早填满,抖动还有更多工作要做,抖动可能会选择不对更大的代码大小进行某些优化,等等.
  • 在被调用者站点,我们将访问堆栈(或寄存器)上的值转换为指向间接.现在,该指针很可能位于缓存中,但是,我们现在已经将对该值的单指令访问转换为双指令访问.
  • 等等.

假设它是a double并且你将它改为in double.同样,现在变量无法注册到高性能浮点寄存器中.这不仅具有性能影响,还可以改变程序行为!允许C#以高于64位的精度执行浮点运算,并且通常只有在可以注册浮点数时才这样做.

这不是免费优化.您必须根据替代方案衡量其绩效.你最好的选择就是不要像设计指南所说的那样首先制作大型结构.

  • 在所有情况下,通过`in`真的相当于通过值吗?如果我们有`f(ref T x,in T y)`和`f`修改`x`,当调用`f(ref a,a)`时,它应该观察到与`y`相同的变化.如果`f`采用`in T y`和委托将在调用时修改`y`,这同样适用.没有`in`,语义会有所不同,因为`y`永远不会改变它的值,我认为,因为它是一个副本. (4认同)
  • 我怀疑OP的非正式含义是“物理地址”,即“描述内存位置的位模式”,与“后端选择用来描述在哪里找到对象的任何东西”形成对比。 (2认同)
  • @chi:没错。如果您玩带有可变别名的危险游戏,那么您可能会遇到麻烦。如果这样做时会痛,请不要这样做。`in` 的*意图*是为了表示“通过引用传递,只读”的概念,这在逻辑上等同于*当你表现得明智时*通过值传递。 (2认同)

小智 6

有.传递a时struct,in关键字允许进行优化,编译器只需要传递指针,而不存在方法更改内容的风险.最后一个是关键 - 它避免了复制操作.在大型结构上,这可以创造一个与众不同的世界.

  • @TomTom再次,*已经涵盖的问题*.它所要求的是,"那么,那么只是在更大的结构中的优势,还是有一些幕后编译器优化使其在其他地方具有吸引力?如果是后者,为什么我不应该让每个参数都进入?" 注意它是如何询问它是否真的对较大的结构有益,或者只是在它有益的时候.它只是*询问它是否对较小的结构有益(如果是的话,然后跟进).你没有回答任何一个问题. (3认同)
  • 这只是重复问题已经说过的内容,并没有回答它提出的实际问题。 (2认同)