在 C# 中,对 ref 参数的赋值何时有效?

Posted

技术标签:

【中文标题】在 C# 中,对 ref 参数的赋值何时有效?【英文标题】:When is assignment to a ref argument effective in C#? 【发布时间】:2017-05-26 13:13:00 【问题描述】:

假设一个方法正在改变一个通过引用传递的参数的值。此类操作的效果是在整个应用程序中立即可见还是仅在方法返回后可见?

下面是一个重要的例子:

int x = 0;
void Foo(ref int y)

    ++y;
    Console.WriteLine(x);

Foo(ref x);

可以在http://csharppad.com/gist/915318e2cc0da2c2533dfa7983119869下的C#Pad中运行

函数Foo 可以访问变量x,因为它在同一个范围内,而且它恰好在调用站点接收到对它的引用。如果++y 的效果是即时的,则输出应该是1,但我可以想象一个编译器生成代码,例如,将本地值存储在寄存器中并在稍后返回之前转储到内存中。语言规范是否确保输出为1,还是允许抖动优化,使输出实现依赖?

【问题讨论】:

这给内置在即时编译器中的优化器带来了偏头痛。但在任何当前版本的抖动中都不足为奇,他们很清楚这个论点可能是一个别名,并且对此持悲观态度。 Fortran 仍然存在的一个原因是,这种别名是明确的。 GCC 中严格的别名规则更改导致lot of havoc。 【参考方案1】:

此类操作的效果是在整个应用程序中立即可见还是仅在方法返回后可见?

立即可见 - 因为基本上,您最终传递的是变量本身,而不是变量的值。您正在修改完全相同的存储位置。

确实,您可以在相同的方法中看到这一点:

using System;

class Test

    static void Main(string[] args)
    
        int a = 10;
        Foo(ref a, ref a);
    

    static void Foo(ref int x, ref int y)
    
        x = 2;
        Console.WriteLine(y); // Prints 2, because x and y share a storage location
    

这是 C# 5 规范中的第 5.1.5 节:

引用参数不会创建新的存储位置。相反,引用参数表示与作为函数成员或匿名函数调用中的参数给出的变量相同的存储位置。因此,引用参数的值始终与基础变量相同。

顺便说一句,反过来也是如此 - 如果基础变量的值以其他方式更改,则该更改将在方法中可见。使用委托更改值的示例:

using System;

class Test

    static void Main(string[] args)
    
        int a = 10;
        Foo(ref a, () => a++);
    

    static void Foo(ref int x, Action action)
    
        Console.WriteLine(x); // 10
        action();             // Changes the value of a
        Console.WriteLine(x); // 11
        x = 5;
        action();
        Console.WriteLine(x); // 6
    

【讨论】:

我们将不胜感激存储位置的含义。我知道它是抽象机器中的一个概念位置,不是存储变量值的物理位置,它几乎可以肯定跨越寄存器、缓存和操作内存。 @MarcinKaczmarek:是的,这是正确的 - 它是存储数据的名义位置。我不想更清楚地说明它:)【参考方案2】:

ref 是存储位置的别名ref 参数指向您传入的完全相同的变量,所以是的,赋值立即可见。

【讨论】:

【参考方案3】:

C# 规范保证在单线程上下文中运行时必须观察到所有操作按顺序发生。因此,您提供的程序输出 0 将是无效的优化,因为这会导致从单个线程观察到重新排序。

当您提供ref 参数时,重点是参数是引用变量的别名。它不是副本;只有在方法完成后才能观察到这些变化。相反,在您的程序中使用y 在语义上与使用x 相同,因为这两个标识符都引用了相同的存储位置。

我会注意到,您的程序仅在使用ref 参数的方法返回后才访问该变量,因此它实际上并没有回答您的问题。根据 ref 参数是否实际上是对同一变量的引用,或者如果它只是在方法末尾将值复制回,实际更改的程序 sn-p 看起来像这样:

public static void Foo(ref int y, Func<int> function)

    y = 42;
    Console.WriteLine(function());


int x = 7;
Foo(ref x, () => x);

【讨论】:

我承认访问变量的时间是有争议的,所以我更新了sn-p。

以上是关于在 C# 中,对 ref 参数的赋值何时有效?的主要内容,如果未能解决你的问题,请参考以下文章

C#中ref和out的使用小结

C#中ref与out使用小结

C#值参数和引用参数,方法的重载,foreach,数组,以及ref和out的用法

传递参数ref与输出参数out

C#中的const形参

out 和 ref + params 的理解