为啥有人会在 C# 中使用“in”参数修饰符?
Posted
技术标签:
【中文标题】为啥有人会在 C# 中使用“in”参数修饰符?【英文标题】:Why would one ever use the "in" parameter modifier in C#?为什么有人会在 C# 中使用“in”参数修饰符? 【发布时间】:2019-03-20 02:18:25 【问题描述】:所以,我(想我)了解 in
参数修饰符的作用。但它所做的似乎是相当多余的。
通常,我认为使用 ref
的唯一原因是修改调用变量,这是 in
明确禁止的。所以通过 in
引用传递似乎在逻辑上等同于按值传递。
有某种性能优势吗?我相信在事物的后端,ref
参数必须至少复制变量的物理地址,它应该与任何典型的对象引用大小相同。
那么,优势只是更大的结构,还是有一些幕后编译器优化使其在其他地方具有吸引力?如果是后者,为什么我不应该将 every 参数设为 in
?
【问题讨论】:
是的,有性能优势。ref
用于通过引用传递 structs 而不是复制它们。 in
表示不应修改结构。
@dbc 不,这与互操作无关
值类型的性能。The New “in” Keyword For C# 7.2
详细讨论here。注意最后一个警告:It means that you should never pass a non-readonly struct as in parameter.
我可以发誓这是重复的,但我再也找不到了
【参考方案1】:
in
最近被引入 C# 语言。
in
实际上是一个ref readonly
。一般而言,in
可以提供帮助的用例只有一个:处理大量大型readonly struct
s 的高性能应用程序。
假设你有:
readonly struct VeryLarge
public readonly long Value1;
public readonly long Value2;
public long Compute()
// etc
和
void Process(in VeryLarge value)
在这种情况下,当在Process
方法中使用该结构时(例如,在调用value.Compute()
时),VeryLarge
结构将通过引用传递而不创建防御性副本,并且结构不变性由编译器。
请注意,使用in
修饰符传递非只读struct
将导致编译器在调用结构的方法并访问上述Process
方法中的属性时创建一个防御性副本,这将对性能产生负面影响!
有一个非常好的MSDN blog entry,我建议仔细阅读。
如果您想了解更多in
-introducing 的历史背景,您可以在 C# 语言的 GitHub 存储库中阅读此 discussion。
一般来说,大多数开发人员都同意引入in
可能被视为错误。这是一种相当奇特的语言功能,只能在高性能边缘情况下使用。
【讨论】:
它是抑制编译器生成的防御性副本,还是仅仅消除程序员在使用ref
的上下文中手动创建防御性副本的需要,例如通过允许someProc(in thing.someProperty);
与propType myProp = thing.someProperty; someProc(ref myProp);
? in
是像 out
这样的纯 C# 概念,还是已添加到 .NET Framework 中?
@supercat,您不能使用in
传递属性,因为in
实际上是具有特殊属性的ref
。所以你的第一个 sn-p 不会编译。 @VisualMelon,没错,防御性复制发生在调用方法或从将结构作为参数的方法中访问结构的属性时。
@dymanoid 很抱歉向您发送垃圾邮件,我花了大约 10 次尝试才理解您写的内容(我认为这不是您的错!)
@dymanoid:in
和 out
都是从一开始就应该在框架中的概念(以及方法和属性可以指示它们是否修改 this
的方法) .编译器可以通过传递对临时持有它的引用来允许将属性传递给in
参数;如果用另一种语言编写的函数修改了该临时函数,则语义会有点恶心,但这将是函数行为与其签名不匹配的错误。
@supercat,请不要在这里展开讨论(反正我不是 .NET Framework 概念团队的成员)。答案中引用了一个很长的讨论,并且 GitHub 讨论引用了其他一些 - 有趣的阅读。顺便说一句,您可以在 VB.NET 中通过引用传递属性 - 编译器会为您创建这些临时文件(当然这可能会导致晦涩的问题)。【参考方案2】:
通过
in
引用传递似乎在逻辑上等同于按值传递。
正确。
有某种性能优势吗?
是的。
我认为,在事物的后端,
ref
参数必须至少复制变量的物理地址,该地址应该与任何典型的对象引用大小相同。
不存在对对象的引用和对变量的引用的大小相同的要求,也没有要求机器字的大小,但是是的,实际上在 32 位机器上都是 32 位,在 64 位机器上都是 64 位。
我不清楚你认为“物理地址”与它有什么关系。在 Windows 上,我们使用虚拟地址,而不是物理地址 em> 在用户模式代码中。在什么情况下你会认为物理地址在 C# 程序中是有意义的,我很想知道。
也没有要求将任何类型的引用实现为存储的虚拟地址。在符合 CLI 规范的实现中,引用可以是对 GC 表的不透明句柄。
优势只是在较大的结构中吗?
降低传递较大结构的成本是该功能的激励方案。
请注意,不能保证in
使任何程序实际上更快,它可以使程序变慢。 所有关于性能的问题都必须通过实证研究来回答。很少有优化是总是获胜;这不是“永远赢”的优化。
是否有一些幕后编译器优化使其在其他地方具有吸引力?
编译器和运行时允许在不违反 C# 规范规则的情况下进行他们选择的任何优化。据我所知,in
参数还没有这样的优化,但这并不排除将来会进行这样的优化。
为什么我不应该将每个参数都设置为 in?
好吧,假设您创建了一个int
参数而不是in int
参数。收取什么费用?
假设它是double
,而您将其更改为in double
。同样,现在无法将变量注册到高性能浮点寄存器中。这不仅会影响性能,还会改变程序行为! C# 允许以高于 64 位的精度进行浮点运算,并且通常仅在可以注册浮点数时才这样做。
这不是免费的优化。您必须根据替代方案衡量其性能。最好的办法是一开始就不要像设计指南建议的那样制作大型结构。
【讨论】:
“你会在什么[...]可能的情况下想象物理地址在C#程序中是有意义的,[...]。”。 C# 通常用于具有内存映射 I/O 的系统,以及诸如重置向量之类的东西。我在这里考虑的是普通的老式 wintel 盒子。使用 x86 处理器和 VGA 帧缓冲区。好的,通常我们不会直接操作这些,但我们可以,对吧? 在所有情况下通过in
传递真的等同于按值传递吗?如果我们有f(ref T x, in T y)
和f
修改x
,它应该在f(ref a,a)
调用时观察到y
的相同变化。如果f
采用in T y
和一个在调用时将修改y
的委托,这同样适用。如果没有in
,语义会有所不同,因为y
的值永远不会改变,我认为,因为它是一个副本。
我怀疑 OP 以非正式的方式表示“物理地址”,作为“描述内存位置的位模式”,与“无论后端选择使用什么来描述在哪里找到一个对象”。
@Wilson:我很想看看你所说的例子;我不知道有谁试图在 C# 中做这些事情。多年来一直有人谈论“系统 C#”,可以使用高级托管语言编写低级系统组件,但我自己从未真正见过这样的代码。
@chi:没错。如果您玩带有变量混叠的危险游戏,那么您可能会遇到麻烦。如果您这样做时会感到疼痛,请不要这样做。 in
的意图是为了表示“通过引用传递,只读”的概念,这在逻辑上等同于在当你的行为明智时传递值。【参考方案3】:
有。当传递struct
时,in
关键字允许编译器只需要传递一个指针的优化,没有方法更改内容的风险。最后一点很关键——它避免了复制操作。在大型结构上,这可以带来不同的世界。
【讨论】:
这只是重复问题已经说过的内容,并没有回答它提出的实际问题。 仅在只读结构中。否则编译器仍然会创建一个防御性副本 其实没有。确认有性能效益。鉴于 IN 是在通过语言的性能运行中创建的,是的,这就是原因。它允许优化(在只读结构上)。 @TomTom 再次,问题已经涵盖。它问的是,“那么,优势只是在更大的结构中,还是有一些幕后编译器优化使其在其他地方具有吸引力?如果是后者,为什么我不应该将每个参数都设置为 in?”请注意,它并没有询问它是否真的对更大的结构有益,或者只是什么时候有益。它只是询问它是否对较小的结构有益(如果是,则进行跟进)。您还没有回答任何一个问题。【参考方案4】:在它是 c# 7.2 中的只读引用
这意味着您不会将整个对象传递给函数堆栈,类似于 ref 情况,您只传递对结构的引用
但尝试更改对象的值会导致编译器错误。
是的,如果您使用大型结构,这将允许您优化代码性能。
【讨论】:
【参考方案5】:这样做是因为函数式编程方法。主要原则之一是函数不应该有副作用,这意味着它不应该改变参数的值并且应该返回一些值。在 C# 中,如果不通过引用进行复制,就无法传递结构(和值类型),这允许更改值。只要方法开始更改其值,就有一种 hacky 算法可以复制结构(它们的集合是结构 BTW)。使用 swift 的人并不都知道复制的东西。这是一个很好的 c# 功能,因为它的内存高效且显式。如果您查看what's new,您会发现越来越多的事情是围绕堆栈中的结构和数组完成的。而 in 声明正是这些功能所必需的。其他答案中提到了一些限制,但对于了解 .net 的发展方向并不是那么重要。
【讨论】:
以上是关于为啥有人会在 C# 中使用“in”参数修饰符?的主要内容,如果未能解决你的问题,请参考以下文章