所以,我(认为我)理解了 in
参数修饰符的作用。但它似乎非常冗余。
通常,我认为使用 ref
的唯一原因是修改调用变量,而这被 in
显式禁止。因此,通过in
引用传递似乎在逻辑上等同于按值传递。
是否存在某种性能优势?我认为,在后端方面,ref
参数至少必须复制变量的物理地址,其大小应与任何典型对象引用相同。
那么,优势只在于更大的结构体中吗,还是有某些幕后编译器优化使其在其他地方具有吸引力?如果是后者,为什么不将in
用于每个参数?
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) { }
在这种情况下,当在Process
方法中使用VeryLarge
结构体(例如调用value.Compute()
)时,该结构体将被按引用传递而无需创建防御性副本,并且编译器可以确保该结构体的不可变性。
请注意,在使用in
修饰符传递非只读struct
时,在上述Process
方法中调用结构体的方法和访问属性时,编译器将会创建一个防御性副本,这将对性能产生负面影响!
有一篇非常好的MSDN博客文章,我建议您仔细阅读。
如果您想获取更多关于in
引入的历史背景,您可以阅读C#语言GitHub存储库中的讨论。
总的来说,大多数开发人员认为引入in
可能是一个错误。这是一个相当奇特的语言特性,只有在高性能边缘情况下才有用。
someProc(in thing.someProperty);
而不是propType myProp = thing.someProperty; someProc(ref myProp);
?'in'是类似于'out'的C#概念,还是已经添加到.NET Framework中? - supercatin
传递属性,因为in
实际上是带有特殊属性的ref
。 所以您的第一个片段将无法编译。 @VisualMelon,没错,防御性复制发生在调用方法或从获取结构体作为参数的方法中访问结构体的属性时。 - dymanoidin
特性当然很令人困惑。我只是试图描述这个特性有意义的(唯一)用例。 - dymanoidin
and out
" 是应该从一开始就出现在框架中的概念,同时也需要一种方法来指示方法和属性是否修改 this
。编译器可以通过传递一个持有该属性的临时引用来将属性传递给 in
参数;如果另一种语言编写的函数修改了该临时变量,语义将有点棘手,但这将是函数行为不匹配其签名的错误。 - supercat按引用传递似乎在逻辑上等价于按值传递。
正确。
这样做是否有性能优势?
有。
我认为在后端方面,
ref
参数至少必须复制变量的物理地址,这应该与任何典型对象引用的大小相同。
没有要求对象引用和变量引用的大小必须相同,也没有要求它们的大小与机器字长相同,但是在实践中,在32位机器上两者都是32位,在64位机器上两者都是64位。
我不清楚您认为“物理地址”与此有何关联。 在 Windows 上,我们在用户模式下使用的是虚拟地址,而不是物理地址。我很好奇在什么情况下您认为物理地址在 C# 程序中是有意义的。
任何一种引用类型的实现并不要求其被实现为存储的虚拟地址。在符合 CLI 规范的实现中,引用可以是指向 GC 表的不透明句柄。
这种优势只存在于较大的结构体中吗?
减少传递较大的结构体的成本是该特性的动机场景。
请注意,并非所有程序都可以通过使用in
参数实现加速,它有可能会使程序变慢。关于性能的所有问题必须通过经验研究来回答。很少有一些优化措施是总是有效的,这不是一个总是有效的优化措施。
是否有某些幕后编译器优化使其在其他地方更具吸引力?
只要不违反C#规范的规则,编译器和运行时可以选择进行任何优化。据我所知,目前还没有针对in
参数的这样的优化,但这并不排除将来出现这样的优化。
为什么我不应该把每个参数都定义为in呢?
好吧,假设您将一个int
参数改为一个in int
参数。会导致哪些成本?
假设它是一个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
永远不会改变其值,因为它是一个副本。 - chiin
的目的是表示“按引用传递,只读”的概念,这在行为明智时相当于按值传递。 - Eric Lippert确实有这样的优化。当传递一个struct
时,关键字in
允许编译器仅需要传递指针,而无需担心方法会更改内容。这点非常重要——它避免了复制操作。对于大型结构体,这可以带来很大的差别。
in 是 C# 7.2 中的只读引用。
这意味着您不会将整个对象传递到函数堆栈中,类似于 ref 的情况,您只传递结构的引用。
但是,试图更改对象的值会导致编译器错误。
是的,如果您使用大型结构,则可以通过此功能优化代码性能。
ref
用于通过引用传递 structs 而不是复制它们。in
表示该结构体不应被修改。 - Panagiotis Kanavos