C# - 初始化器内类字段的闭包?
考虑以下代码:(
using System;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var square = new Square(4);
Console.WriteLine(square.Calculate());
}
}
class MathOp
{
protected MathOp(Func<int> calc) { _calc = calc; }
public int Calculate() { return _calc(); }
private Func<int> _calc;
}
class Square : MathOp
{
public Square(int operand)
: base(() => _operand * _operand) // runtime exception
{
_operand = operand;
}
private int _operand;
}
}
忽略类设计;我实际上并没有编写计算器!此代码仅代表一个需要一段时间才能缩小范围的更大问题的最小重现)
我希望它能够:
- 打印“16” ,或者
- 如果在这种情况下不允许关闭成员字段,则抛出编译时错误
相反,我在指定的行抛出一个无意义的异常。在 3.0 CLR 上,它是一个 NullReferenceException;在 Silverlight CLR 上,这是臭名昭著的操作可能会破坏运行时的稳定性。
Consider the following code:
using System;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var square = new Square(4);
Console.WriteLine(square.Calculate());
}
}
class MathOp
{
protected MathOp(Func<int> calc) { _calc = calc; }
public int Calculate() { return _calc(); }
private Func<int> _calc;
}
class Square : MathOp
{
public Square(int operand)
: base(() => _operand * _operand) // runtime exception
{
_operand = operand;
}
private int _operand;
}
}
(ignore the class design; I'm not actually writing a calculator! this code merely represents a minimal repro for a much bigger problem that took awhile to narrow down)
I would expect it to either:
- print "16", OR
- throw a compile time error if closing over a member field is not allowed in this scenario
Instead I get a nonsensical exception thrown at the indicated line. On the 3.0 CLR it's a NullReferenceException; on the Silverlight CLR it's the infamous Operation could destabilize the runtime.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这是一个已修复的编译器错误。该代码从一开始就不应该是合法的,如果我们要允许它,我们至少应该生成有效的代码。我的不好。对于给您带来的不便,我们深表歉意。
It was a compiler bug that has been fixed. The code should never have been legal in the first place, and if we were going to allow it, we should have at least generated valid code. My bad. Sorry about the inconvenience.
它不会导致编译时错误,因为它是一个有效的闭包。
问题是在创建闭包时
this
尚未初始化。当提供该参数时,您的构造函数尚未实际运行。因此,产生的 NullReferenceException 实际上是非常合乎逻辑的。this
就是null
!我会向你证明这一点。让我们这样重写代码:
猜猜这会打印什么?是的,它是
true
,闭包返回null
,因为this
在执行时没有初始化。编辑
我对 Thomas 的声明很好奇,认为他们可能在后续的 VS 版本中改变了行为。我实际上发现了 Microsoft Connect 问题 关于这件事。它被关闭为“无法修复”。奇怪的。
正如微软在回应中所说,在基本构造函数调用的参数列表中使用 this 引用通常是无效的;该引用在该时间点根本不存在,如果您尝试“裸”使用它,您实际上会收到编译时错误。因此,可以说它应该为闭包情况产生编译错误,但是
this
引用对编译器是隐藏的,编译器必须知道(至少在 VS 2008 中)在封闭物内寻找它,以防止人们这样做。事实并非如此,这就是为什么你最终会出现这种行为。It's not going to result in a compile-time error because it is a valid closure.
The problem is that
this
is not initialized yet at the time the closure is created. Your constructor hasn't actually run yet when that argument is supplied. So the resultingNullReferenceException
is actually quite logical. It'sthis
that'snull
!I'll prove it to you. Let's rewrite the code this way:
Guess what this prints? Yep, it's
true
, the closure returnsnull
becausethis
is not initialized when it executes.Edit
I was curious about Thomas's statement, thinking that maybe they'd changed the behaviour in a subsequent VS release. I actually found a Microsoft Connect issue about this very thing. It was closed as "won't fix." Odd.
As Microsoft says in their response, it is normally invalid to use the
this
reference from within the argument list of a base constructor call; the reference simply does not exist at that point in time and you will actually get a compile-time error if you try to use it "naked." So, arguably it should produce a compile error for the closure case, but thethis
reference is hidden from the compiler, which (at least in VS 2008) would have to know to look for it inside the closure in order to prevent people from doing this. It doesn't, which is why you end up with this behaviour.这个怎么样:
How about this:
您是否尝试过使用
() =>操作数 * 操作数
代替?问题在于,不确定 _operand 在您调用基数时是否会被设置。是的,它试图在您的方法上创建一个闭包,并且不能保证这里的顺序。由于您根本没有设置 _operand,我建议仅使用
() =>;操作数 * 操作数
代替。Have you tried using
() => operand * operand
instead? The issue is that there's no certainty that _operand will be set by the time you call the base. Yes, it's trying to create a closure on your method, and there's no guarantee of the order of things here.Since you're not setting _operand at all, I'd recommend just using
() => operand * operand
instead.