C#中的'out'或'ref'参数何时实际返回给调用者?

14

当我给一个 outref 参数分配一个值时,该值是否立即被分配给调用方提供的引用,还是在方法返回时将outref参数值分配给引用?如果方法抛出异常,值会被返回吗?

例如:

int callerOutValue = 1;
int callerRefValue = 1;
MyMethod(123456, out callerOutValue, ref callerRefValue);

bool MyMethod(int inValue, out int outValue, ref int refValue)
{
    outValue = 2;
    refValue = 2;

    throw new ArgumentException();

    // Is callerOutValue 1 or 2?
    // Is callerRefValue 1 or 2?
}
2个回答

26

refout参数允许方法使用调用者传入的实际引用,因此对这些引用的所有更改在控制返回给调用者时立即反映。

这意味着在您上面的示例中(如果您当然要捕获ArgumentException),outValuerefValue都将设置为2。

还需要注意的是,在IL级别上,outref是相同的概念 - 只有C#编译器强制要求out的额外规则,要求方法在返回之前设置其值。 因此,从CLR的角度来看,outValuerefValue具有相同的语义,并且以相同的方式处理。


1
嗯...这不是我预期的结果,但测试证实了它!+1。 - Michael Bray
我记得微调一个接收 out 参数的方法。该方法在非常紧密的循环中被调用,我想消除每次调用时所需的初始化,因为参数没有改变,所以我将其改为使用 ref 参数,但它的性能明显变差了。有人能解释一下这是为什么吗?还是这只是个偶然事件? - JulianR
Julian:当您使用 out 参数时,编译器(和 IL)不会提前初始化变量 - 只需使用它即可。而对于 ref,则必须在调用之前初始化参数。这会增加指令。 - Reed Copsey
我同意。但情况是这样的,我最终以每秒数百万次调用带有 out 参数的方法结束,而在该方法中,我每次都初始化该变量(由于它是一个 out 参数,所以被迫这样做),因此我想我可以通过使用 ref 并仅初始化一次变量来优化它,而不是数百万次,因为它没有改变(只是用它来避免复制大struct),但速度却更慢了。 - JulianR

14

安德鲁是正确的;我只会增加一些额外的细节。

首先,正确的理解out/ref参数的方式是它们是变量的别名。也就是说,当你有一个方法M(ref int q)并调用它M(ref x)时,q和x是完全相同的变量的两个不同名称。变量是存储位置;当你将一些东西存储在q中时,因为它们是相同位置的两个不同名称,所以你也将其存储在x中。

其次,你描述的替代方案称为“复制引用”。在这个模式下,有两个存储位置,并且一个在函数调用开始时被复制到另一个中,在完成时再被复制回来。正如你所指出的那样,当抛出异常时,复制引用的语义与别名引用的语义不同。

它们在奇怪的情况下也是不同的,比如这种情况:

void M(ref int q, ref int r)
{  
  q = 10;
  r = 20;
  print (q);
}

...

M(ref x, ref x);
在别名情况下,x、q和r都是相同的存储位置,因此输出20。在复制-输入-复制-输出引用中,这将打印10,并且x的最终值取决于复制输出是从左到右还是从右到左进行。最后,如果我记得正确,实现表达式树时存在一些罕见而奇怪的场景,我们实际上对ref参数实现了复制-输入-复制-输出语义。我应该审查一下那段代码,看看是否能记起那些具体情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接