ref和out在运行时之间有什么区别?


15

C#提供refout关键字,以使参数通过引用传递。两者的语义非常相似。唯一的区别在于标记变量的初始化:

  • ref要求在将变量传递给函数之前进行初始化,out而不是。
  • out要求变量在函数内部初始化,ref不需要。

这两个关键字的用例也几乎相同,它们的使用频率太高了,我相信这是代码的味道(尽管有诸如TryParseTryGetValue模式之类的有效用例)。

因此,有人可以解释一下,为什么C#中有两个非常相似的工具用于如此狭窄的用例?

另外,在MSDN上,它们具有不同的运行时行为:

尽管ref和out关键字导致不同的运行时行为,但在编译时它们不被视为方法签名的一部分。

它们的运行时行为有何不同?

结论

这两个答案看起来都是正确的,谢谢你们。我接受jmoreno是因为它更明确。


我的猜测是,它们的实现方式都与refC ++ 中的实现方式相同。指向相关对象指针(或原始对象)的指针。即Int32.TryParse(myStr, out myInt)(C#)的执行方式与int32_tryParse(myStr, &myInt)(C)相同;唯一的区别是编译器为防止错误而强制执行一些约束。(我不会将其发布为答案,因为我可能对此不太了解,但我想这是可行的(因为它是有道理的))
科尔·约翰逊

Answers:


10

在问这个问题时,MSDN文章有点不正确(此问题已得到纠正)。代替“引起不同的行为”,它应该是“要求不同的行为”。

特别是,即使使用相同的机制(IL)来启用该行为,编译器也对两个关键字强制执行不同的要求。


因此,我的理解是,编译器会检查诸如取消引用out参数或不分配ref参数之类的事情,但是它发出的IL只是两种方式的引用。那是对的吗?
安德鲁

顺便说一句,从事实可以看出这一点,out T并且ref T它们在其他CLI语言(例如VB.Net或C ++ / CLI)中呈现相同。
ACH

@AndrewPiliser:正确。假设代码以任何一种方式编译(您不应该在不应该使用的时候取消引用),则IL将是相同的。
jmoreno

4

这是有关该主题的有趣文章,可能会回答您的问题:

http://www.dotnetperls.com/ref

兴趣点:

“ ref和out之间的区别不在Common Language Runtime中,而在于C#语言本身。”

更新:

我工作中的高级开发人员刚刚证实了@jmoreno的答案,因此请确保您已阅读!


因此,如果我正确理解,IL级别上就没有区别,这意味着它们不能具有不同的运行时行为。这将与MSDN所说的相矛盾。尽管如此,本文仍然很有趣!
的Gabor Angyal

@GáborAngyal您偶然碰巧没有MSDN文章的链接,对吗?如果意见分歧,最好在这里提出
Dan Beaulieu

在这里你去:msdn.microsoft.com/en-gb/library/t3c3bfhx.aspx,但它是我的问题一直:)
的Gabor Angyal

1
string一切有什么关系?
罗伯特·哈维

这只是文章的一个表述。更新以删除第二行。–
Dan Beaulieu 2015年

2

运行时?绝对没有。您不能重载仅具有ref或out关键字而具有相同参数的方法。

尝试对此进行编译,将出现编译错误“已声明具有相同签名的方法”:

    private class MyClass
    {
        private void DoSomething(out int param)
        {
        }
        private void DoSomething(ref int param)
        {
        }
    }

要回答这个问题: “ ...为什么C#中有两个非常相似的工具用于如此狭窄的用例?”

从代码的可读性和API的角度来看,两者之间存在巨大差异。作为API的使用者,我知道使用“ out”时,API不依赖于out参数。作为API开发人员,我更喜欢“ out”,并且仅在绝对必要(很少!)时才使用“ ref”。请参阅此参考资料进行详细讨论:

/programming/1516876/when-to-use-ref-vs-out

支持信息:我编译了以下方法并将其分解。我使用了ref和out关键字(在本示例中为out),但是除了地址引用之外,汇编代码没有发生变化,正如我期望的那样:

    private class MyClass
    {
        internal void DoSomething(out int param)
        {
            param = 0;
        }
    }

00000000 push ebp
00000001 mov ebp,esp
00000003 push edi 00000004 push
esi
00000005 push ebx
00000006 sub esp,34h
00000009 xor eax,eax
0000000b mov dword ptr [ebp-10h],eax
0000000e mov dword ptr [ebp-1Ch],eax
00000011 mov dword ptr [ebp-3Ch],ecx
00000014 mov dword ptr [ebp-40h],edx
00000017 cmp dword ptr ds:[008B1710h],0
0000001e je 00000025
00000020 call 6E6B601E
00000025 nop
param = 0;
00000026 MOV EAX,DWORD PTR [
EBP - 40H ] 00000029 XOR EDX,EDX
0000002b mov dword ptr [eax],edx
}
0000002d nop
0000002e lea esp,[ebp-0Ch]
00000031 pop ebx
00000032 pop esi
00000033 pop edi
00000034 pop ebp
00000035 ret

我是否正确阅读了组装件?


您所写的内容是正确的,但是请注意,这并没有明显的含义,因为没有运行时差异,因为不允许这种重载。
的Gabor Angyal

啊-好点!谁能反汇编上面的代码并证明我是对还是错?我在阅读汇编方面很糟糕,但很想知道。
Shmoken

好的-我使用“ out”和“ ref”反汇编了代码,但我的DoSomething方法看不出任何区别:
Shmoken
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.