对ref参数的赋值在C#中何时有效?
假设一个方法正在更改通过引用传递的参数的值。这种操作的效果是在整个应用程序中立即可见还是仅在方法返回后可见 下面是一个重要的例子:对ref参数的赋值在C#中何时有效?,c#,optimization,semantics,jit,C#,Optimization,Semantics,Jit,假设一个方法正在更改通过引用传递的参数的值。这种操作的效果是在整个应用程序中立即可见还是仅在方法返回后可见 下面是一个重要的例子: int x = 0; void Foo(ref int y) { ++y; Console.WriteLine(x); } Foo(ref x); 它可以在C#Pad下运行 函数Foo可以访问变量x,因为它在同一范围内,而且它恰好在调用站点接收到对它的引用。如果++y的效果是即时的,那么输出应该是1,但我可以想象编译器生成的代码,例如,将本地值存储
int x = 0;
void Foo(ref int y)
{
++y;
Console.WriteLine(x);
}
Foo(ref x);
它可以在C#Pad下运行
函数Foo
可以访问变量x
,因为它在同一范围内,而且它恰好在调用站点接收到对它的引用。如果++y
的效果是即时的,那么输出应该是1
,但我可以想象编译器生成的代码,例如,将本地值存储在寄存器中,并在稍后返回之前转储到内存中。语言规范是否确保输出为1
或允许抖动进行优化,从而使输出实现依赖于它?C规范确保在单线程上下文中运行时必须遵守所有操作的顺序。因此,您提供的程序输出0
将是无效的优化,因为这将导致从单个线程观察到的重新排序
当您提供ref
参数时,关键是该参数是引用变量的别名。它不是复制品;只有在方法完成后,才能观察到更改。相反,在程序中使用y
在语义上与使用x
完全相同,因为两个标识符引用相同的存储位置
我将注意到,您的程序只在使用ref
参数的方法返回后访问变量,因此它实际上不会回答您的问题。根据ref
参数是否实际是对同一变量的引用,或者如果它只是将值复制回方法末尾,实际更改的程序段将如下所示:
public static void Foo(ref int y, Func<int> function)
{
y = 42;
Console.WriteLine(function());
}
C#规范保证在单线程上下文中运行时,所有操作都必须按顺序进行。因此,您提供的程序输出0
将是无效的优化,因为这将导致从单个线程观察到的重新排序
当您提供ref
参数时,关键是该参数是引用变量的别名。它不是复制品;只有在方法完成后,才能观察到更改。相反,在程序中使用y
在语义上与使用x
完全相同,因为两个标识符引用相同的存储位置
我将注意到,您的程序只在使用ref
参数的方法返回后访问变量,因此它实际上不会回答您的问题。根据ref
参数是否实际是对同一变量的引用,或者如果它只是将值复制回方法末尾,实际更改的程序段将如下所示:
public static void Foo(ref int y, Func<int> function)
{
y = 42;
Console.WriteLine(function());
}
ref
是存储位置的别名。ref
参数指向与您传入的变量完全相同的变量,因此可以立即看到赋值。ref
是存储位置的别名。ref
参数指向与您传入的变量完全相同的变量,因此可以立即看到赋值
这种操作的效果是在整个应用程序中立即可见还是仅在方法返回后可见
它是立即可见的——因为基本上,您最终传递的是变量本身,而不是变量的值。您正在修改完全相同的存储位置
事实上,您可以在相同的方法中看到这一点:
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
}
}
这种操作的效果是在整个应用程序中立即可见还是仅在方法返回后可见
它是立即可见的——因为基本上,您最终传递的是变量本身,而不是变量的值。您正在修改完全相同的存储位置
事实上,您可以在相同的方法中看到这一点:
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
}
}
这让内置在即时编译器中的优化器头疼不已。但在抖动的任何当前版本中都没有令人惊讶的地方,他们很清楚这个参数可能是一个别名,并且对此持悲观态度。Fortran仍然存在的一个原因是,这种别名是明确的。GCC中严格的别名规则更改导致了一个错误。这让内置在即时编译器中的优化器头疼不已。但在抖动的任何当前版本中都没有令人惊讶的地方,他们很清楚这个参数可能是一个别名,并且对此持悲观态度。Fortran仍然存在的一个原因是,这种别名是明确的。GCC中的严格别名规则更改原因