在 C# 中,为什么我不能使用其地址填充局部变量,然后再使用该变量?
In C#, why can't I populate a local variable using its address, then use the variable later?
考虑以下代码:
private unsafe void Function()
{
int length;
// This line raises error CS1686, "Local 'length' or its members cannot have their address taken and be used inside an anonymous method or lambda expression".
glGetProgramiv(1, GL_PROGRAM_BINARY_LENGTH, &length);
FunctionWithLambda(() => Console.WriteLine(length));
}
private void FunctionWithLambda(Action callback)
{
callback();
}
请注意,我正在使用 length
(局部变量)的地址,然后使用 变量本身(不是 它的地址)在 lambda 中。我理解为什么不能在 lambda 中直接使用局部变量地址 (请参阅 , among other examples), but why can't I use the value of length
once assigned (even if that assignment happens to use the &
operator)? The official documentation for error CS1686 (https://docs.microsoft.com/bs-latn-ba/dotnet/csharp/misc/cs1686)尚未澄清这种混淆。
我的假设是这只是语言限制,但我很好奇是否存在我遗漏的潜在技术原因。另请注意,我不是在询问 如何解决此问题(我知道我可以先轻松地将 length
复制到另一个局部变量)。
The C# specification 表示如下 (我的粗体):
23.4 Fixed and moveable variables
The address-of operator (§23.6.5) and the fixed
statement (§23.7) divide variables into two categories:
Fixed variables and moveable variables.
...snip...
The &
operator (§23.6.5) permits the address of a fixed variable to be obtained without restrictions. However, because a moveable variable is subject to relocation or disposal by the garbage collector, the address of a moveable variable can only be obtained using a fixed
statement (§23.7), and that address remains valid only for the duration of that fixed
statement.
In precise terms, a fixed variable is one of the following:
- A variable resulting from a simple-name (§12.7.3) that refers to a local variable, value parameter, or parameter array, unless the variable is captured by an anonymous function (§12.16.6.2).
- .....
所以它被规范明确禁止。至于为什么是禁止的,这个你得问语言设计者,但考虑到捕获变量涉及的复杂程度,这有点合乎逻辑。
我想原因很简单:编译太复杂。
编译器必须解决 2 个问题:
- 为匿名方法生成一个 clourse。
- 同步变量的值。
假设以下代码有效。
unsafe void Function()
{
int length = 1;
void bar() => Console.WriteLine(length);
bar();
foo(&length);
bar();
}
unsafe void foo(int* i) { (*i)++; }
预期结果是:
1
2
为了解决第一个问题,C# 将生成一个匿名的 class 来保存上值。
这是伪代码:
class _Anonymous
{
public int _length;
public void _bar() { Console.WriteLine(_length); }
}
unsafe void Function()
{
int length = 1;
var a = new _Anonymous { _length = length };
a._bar();
foo(&length);
a._bar();
}
为了解决第二个问题,C#使用生成的字段而不是原来的局部变量。
unsafe void Function()
{
//int length = 1;
var a = new _Anonymous { _length = 1 };
a._bar();
foo(&a._length);
a._bar();
}
这些都是编译器可以做的工作。但是到现在代码还是不行,我们需要一个额外的固定块。
unsafe void Function()
{
var a = new _Anonymous { _length = 1 };
a._bar();
fixed (int* p = &a._length)
foo(p);
a._bar();
}
所以可以使用更智能的编译器消除限制,但如果我们禁止此类代码,事情就会变得更容易。
考虑以下代码:
private unsafe void Function()
{
int length;
// This line raises error CS1686, "Local 'length' or its members cannot have their address taken and be used inside an anonymous method or lambda expression".
glGetProgramiv(1, GL_PROGRAM_BINARY_LENGTH, &length);
FunctionWithLambda(() => Console.WriteLine(length));
}
private void FunctionWithLambda(Action callback)
{
callback();
}
请注意,我正在使用 length
(局部变量)的地址,然后使用 变量本身(不是 它的地址)在 lambda 中。我理解为什么不能在 lambda 中直接使用局部变量地址 (请参阅 length
once assigned (even if that assignment happens to use the &
operator)? The official documentation for error CS1686 (https://docs.microsoft.com/bs-latn-ba/dotnet/csharp/misc/cs1686)尚未澄清这种混淆。
我的假设是这只是语言限制,但我很好奇是否存在我遗漏的潜在技术原因。另请注意,我不是在询问 如何解决此问题(我知道我可以先轻松地将 length
复制到另一个局部变量)。
The C# specification 表示如下 (我的粗体):
23.4 Fixed and moveable variables
The address-of operator (§23.6.5) and the
fixed
statement (§23.7) divide variables into two categories:
Fixed variables and moveable variables....snip...
The
&
operator (§23.6.5) permits the address of a fixed variable to be obtained without restrictions. However, because a moveable variable is subject to relocation or disposal by the garbage collector, the address of a moveable variable can only be obtained using afixed
statement (§23.7), and that address remains valid only for the duration of thatfixed
statement.In precise terms, a fixed variable is one of the following:
- A variable resulting from a simple-name (§12.7.3) that refers to a local variable, value parameter, or parameter array, unless the variable is captured by an anonymous function (§12.16.6.2).
- .....
所以它被规范明确禁止。至于为什么是禁止的,这个你得问语言设计者,但考虑到捕获变量涉及的复杂程度,这有点合乎逻辑。
我想原因很简单:编译太复杂。
编译器必须解决 2 个问题:
- 为匿名方法生成一个 clourse。
- 同步变量的值。
假设以下代码有效。
unsafe void Function()
{
int length = 1;
void bar() => Console.WriteLine(length);
bar();
foo(&length);
bar();
}
unsafe void foo(int* i) { (*i)++; }
预期结果是:
1
2
为了解决第一个问题,C# 将生成一个匿名的 class 来保存上值。
这是伪代码:
class _Anonymous
{
public int _length;
public void _bar() { Console.WriteLine(_length); }
}
unsafe void Function()
{
int length = 1;
var a = new _Anonymous { _length = length };
a._bar();
foo(&length);
a._bar();
}
为了解决第二个问题,C#使用生成的字段而不是原来的局部变量。
unsafe void Function()
{
//int length = 1;
var a = new _Anonymous { _length = 1 };
a._bar();
foo(&a._length);
a._bar();
}
这些都是编译器可以做的工作。但是到现在代码还是不行,我们需要一个额外的固定块。
unsafe void Function()
{
var a = new _Anonymous { _length = 1 };
a._bar();
fixed (int* p = &a._length)
foo(p);
a._bar();
}
所以可以使用更智能的编译器消除限制,但如果我们禁止此类代码,事情就会变得更容易。