何时赋予在C#中有效的ref参数?

时间:2017-01-11 16:36:55

标签: c# optimization semantics jit

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

下面是一个重要的例子:

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或是否允许抖动优化,使输出实现依赖?

3 个答案:

答案 0 :(得分:7)

  

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

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

事实上,你可以在同一种方法中看到这一点:

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
    }
}

这是第5.1.5节中的C#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
    }
}

答案 1 :(得分:2)

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

答案 2 :(得分:1)

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

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

我注意到您的程序只在使用ref参数的方法返回后访问变量,因此它实际上并没有回答您的问题。实际上根据ref参数实际上是对同一变量的引用而更改的程序片段,或者它是否只是在方法结束时将值复制回来的程序片段,看起来像这样:

public static void Foo(ref int y, Func<int> function)
{
    y = 42;
    Console.WriteLine(function());
}
int x = 7;
Foo(ref x, () => x);