为什么有人会在C#中使用“ in”参数修饰符?

时间:2018-10-15 15:46:47

标签: c# c#-7.2

因此,我(我想)了解了 in 参数修饰符的作用。但是它的确显得很多余。

通常,我认为使用 ref 的唯一原因是修改调用变量, in明确禁止使用该变量/ strong>。因此,通过 in 引用传递在逻辑上等效于按值传递。

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

那么,优点是仅在较大的结构中出现,还是在后台进行了一些编译器优化,使其在其他地方有吸引力?如果是后者,为什么我不应该将每个参数都设置为 in

5 个答案:

答案 0 :(得分:49)

in最近被引入C#语言。

in实际上是ref readonly。一般来说,只有in会有用的一种用例:处理大量大型readonly struct的高性能应用。

假设您拥有:

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

    public long Compute() { }
    // etc
}

void Process(in VeryLarge value) { }

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

请注意,使用struct修饰符传递非只读in会导致编译器在调用struct的方法并访问{{ 1}}方法,会对性能产生负面影响!

有一个非常好的MSDN blog entry,建议您仔细阅读。

如果您想了解Process的更多历史背景介绍,可以在C#语言的GitHub存储库中阅读此discussion

通常,大多数开发人员都同意将in引入是错误的。这是一种相当奇特的语言功能,仅在高性能的情况下有用。

答案 1 :(得分:18)

  

通过in引用传递在逻辑上似乎等效于按值传递。

正确。

  

是否有某种性能优势?

是的

  

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

没有 requirement ,对对象的引用和对变量的引用都具有相同的大小,并且没有 requirement 或两者都是机器字的大小,但是可以,实际上在32位机器上都是32位,在64位机器上都是64位。

我不清楚您认为“物理地址”与它有什么关系。在Windows上,我们使用虚拟地址,而不是物理地址在用户模式代码中。我想知道在什么可能的情况下,物理地址在C#程序中是有意义的。

也没有要求将任何类型的引用实现为存储的虚拟地址。在符合CLI规范的实现中,对GC表的引用可能是不透明的句柄。

  

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

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

请注意,不能保证in实际上会使任何程序变快,并且会使程序变慢。 所有有关绩效的问题都必须通过实证研究来回答总是取胜的优化很少;这不是“永远获胜”的优化。

  

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

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

  

为什么不应该将每个参数都设为in?

好吧,假设您创建了一个int参数而不是一个in int参数。收取什么费用?

  • 呼叫站点现在需要一个变量而不是一个
  • 该变量无法注册。抖动的精心调整的寄存器分配方案刚刚投入了一把扳手。
  • 调用站点上的代码较大,因为它必须对变量进行引用,然后将其放入堆栈中,而在此之前,可以将值简单地推入调用栈中
  • 更大的代码意味着某些短跳转指令现在可能已经变成了长跳转指令,因此代码又变大了。这会对各种事物产生连锁反应。缓存会更快被填满,抖动有更多的工作要做,抖动可能会选择不对较大的代码大小进行某些优化,等等。
  • 在被调用方站点,我们已将对堆栈(或寄存器)上的值的访问变成了对指针的间接访问。现在,该指针很有可能位于缓存中,但是仍然,我们现在将对值的单指令访问变成了两指令访问。
  • 以此类推。

假设它是double,而您将其更改为in double。同样,现在无法将变量注册到高性能浮点寄存器中。这不仅具有 performance 的含义,还可以改变程序的行为!允许C#进行高于64位精度的浮点运算,并且通常只有在可以注册浮点数的情况下才这样做。

这不是免费的优化。您必须对照其他方案评估其性能。最好的选择是,首先不要按照设计准则的建议首先制作大型结构。

答案 2 :(得分:5)

有。传递struct时,使用in关键字进行优化,其中编译器只需要传递一个指针,没有方法更改内容的风险。最后一点很关键—避免了复制操作。在大型结构上,这可以带来很大的不同。

答案 3 :(得分:1)

之所以这样做是因为采用了功能编程方法。主要原理之一是函数不应具有副作用,这意味着它不应更改参数的值并应返回一些值。在C#中,没有传递结构(和值类型)的方法,只能通过引用进行复制,而引用只能更改值。只要方法开始更改其值,就会有一种hacky算法可以复制struct(它们的集合是struct BTW)。使用swift的人并非所有人都知道复制的东西。这是一个不错的c#功能,因为它的内存高效且显式。如果您查看what's new,您会发现围绕堆栈中的结构和数组所做的事情越来越多。而声明只是这些功能所必需的。其他答案中提到了一些限制,但对于了解.net的去向并不是那么重要。

答案 4 :(得分:-1)

中,它是c#7.2中的只读引用

这意味着您不会像 ref 那样将整个对象传递给函数堆栈,而仅将对结构的引用传递给

但是尝试更改对象的值会导致编译器错误。

是的,如果使用大型结构,这将使您能够优化代码性能。