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


84

因此,我(认为我)了解in参数修饰符的作用。但是它的作用似乎是多余的。

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

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

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


1
是的,有性能优势。ref用于通过引用传递结构,而不是复制它们。in表示不应修改该结构。
Panagiotis Kanavos '18 -10-15

@dbc不,这与互操作无关
Panagiotis Kanavos


1
这里详细讨论。注意最后的警告:It means that you should never pass a non-readonly struct as in parameter.
Panagiotis Kanavos '18 -10-15

我可以发誓这是重复的,但是我找不到了
JAD

Answers:


80

in 最近被引入C#语言。

in实际上是一个ref readonly。一般而言,只有一种用in例会有所帮助:处理大量readonly structs的高性能应用程序。

假设您有:

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带有in修饰符的非只读将导致编译器在调用struct的方法并访问上述方法中的属性时创建防御性副本Process,这会对性能产生负面影响!

我建议您仔细阅读一个非常好的MSDN博客条目

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

一般而言,大多数开发人员都认为引入in可能会被视为错误。这是一种非常奇特的语言功能,仅在高性能的情况下才有用。


它是抑制编译器生成的防御性副本,还是仅消除程序员在需要使用的上下文中手动创建防御性副本的需求ref,例如通过允许someProc(in thing.someProperty);vs propType myProp = thing.someProperty; someProc(ref myProp);?是in仅C#的概念out,还是已添加到.NET Framework?
超级猫

@supercat,您不能使用传递属性in,因为in它实际上ref具有特殊属性。因此,您的第一个代码段将无法编译。@VisualMelon,是的,防御性复制发生在调用方法或从将结构作为参数的方法内部访问结构的属性时。
dymanoid

@dymanoid抱歉向您发送垃圾邮件,我花了大约10次尝试来了解您的内容(我认为这不是您的错!)
VisualMelon

4
@dymanoid:inout都是从一开始就应该在框架中的概念(以及方法和属性可以指示它们是否被修改的手段this)。编译器可以in通过将引用传递给临时保存该属性的属性来允许将属性传递给该参数。如果用另一种语言编写的功能修改了该临时功能,则语义会有些刺耳,但这是功能行为与其签名不匹配的错误。
超级猫

1
@supercat,请不要在此处展开​​讨论(无论如何我都不在.NET Framework概念团队中)。答案中引用了很长的讨论,而GitHub讨论则引用了其他一些有趣的阅读内容。顺便说一句,您可以在VB.NET中通过by-ref传递属性-编译器会为您创建这些临时属性(这当然可能导致晦涩的问题)。
dymanoid

41

in逻辑上看,按引用传递等效于按值传递。

正确。

有某种性能优势吗?

是。

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

没有一个需求,要一个对象的引用和参考的变量都为相同的大小,并且没有一个要求,要么是一个机器字的大小,但是,在实践中都在32 32位位机器和64位机器上的64位。

您认为“实际地址”与之相关的事情对我来说还不清楚。在Windows上,我们在用户模式代码中使用虚拟地址,而不是物理地址。我很奇怪地知道,在什么可能的情况下,物理地址在C#程序中是有意义的。

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

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

减少通过较大结构的成本是该功能的推动方案

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

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

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

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

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

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

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

这不是免费的优化。您必须对照其他方案评估其性能。最好的选择是,首先不要像设计指南所建议的那样首先构建大型结构。


“在可能的情况下,您会想象物理地址在C#程序中是有意义的,” [...] 。C#通常用于具有内存映射I / O的系统,以及诸如复位向量之类的东西。我在这里考虑的是普通的老式Wintel盒。使用x86处理器和VGA帧缓冲区。好的,通常我们不直接操作这些,但是可以,对吧?
奥马尔

5
通过传递in真的等同于价值在所有情况下传球?如果我们有f(ref T x, in T y)并进行f修改x,则y在调用时应该观察到与相同的更改f(ref a,a)。如果f接受in T y和会y在调用时进行修改的委托,则同样适用。没有in,语义会有所不同,因为y我认为它的价值永远不会改变,因为它会是一个副本。

2
我怀疑OP的非正式含义是“物理地址”,即“描述存储器位置的位模式”,与“后端选择用来描述在哪里找到对象的任何东西”形成对比。
Sneftel

@Wilson:我很想看看你在说什么的例子;我不知道有人尝试用C#来做这些事情。多年来,人们一直在谈论“系统C#”,该系统可以使用高级托管语言编写低级系统组件,但是我自己从未真正看到过这样的代码。
埃里克·利珀特

2
@chi:是的。如果您玩带有可变别名的危险游戏,则可能会遇到麻烦。如果这样做时很痛,请不要这样做。的意图in是“按引用通,只读”来表示的概念,它在逻辑上等同于由值传递时表现明智
埃里克·利珀特

6

有。传递a时structin关键字允许进行优化,其中编译器仅需要传递一个指针,而不会冒该方法更改内容的风险。最后一点很关键-避免了复制操作。在大型结构上,这可以带来很大的不同。


2
这只是在重复已经说过的问题,而没有回答所问的实际问题。
Servy '18 -10-15

仅在只读结构中。否则,编译器仍将创建防御性副本
Panagiotis Kanavos,

1
其实不行 可以肯定的是,它具有良好的性能。鉴于IN是通过语言运行的效果而创建的,是的,这就是原因。它允许优化(在只读结构上)。
TomTom

3
@TomTom再次说明了该问题。它要求的是:“那么,优点仅在于更大的结构,还是在后台进行了一些编译器优化,使其在其他地方有吸引力?如果是后者,为什么我不应该将每个参数都设为in?” 请注意,它不是在问它对大型结构是否真正有用,还是什么时候才有用。它只是问,如果是较小的结构有益的(再跟进,如果是)。您没有回答任何一个问题。
Servy

2

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


2

它是在C#7.2只读参考

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.