在基本构造函数中使用 lambda 表达式的极端情况
在我们正在构建的框架中,我们需要以下模式:
public class BaseRenderer
{
Func<string> renderer;
public BaseRenderer(Func<string> renderer)
{
this.renderer = renderer;
}
public string Render()
{
return renderer();
}
}
public class NameRenderer : BaseRenderer
{
public string Name{ get; set; }
public NameRenderer ()
: base(() =>this.Name)
{}
}
如您所见,我们在调用基本构造函数时创建一个 lambda。
public class Program
{
public static void Main()
{
Console.WriteLine(new NameRenderer(){Name = "Foo"}.Render());
}
}
奇怪的是,当尝试实际使用 lambda 时,它会抛出 NullReferenceException(控制台应用程序)或某种 ExecutionEngineException 异常(IIS 上的 Web 应用程序)。
我认为原因是在调用基本构造函数之前 this 指针还没有准备好,因此 lambda 在这个阶段无法捕获 this.Name
。
它不应该在“捕获时间”而不是“执行时间”抛出异常吗? 这种行为有记录吗?
我可以用不同的方式重构代码,但我认为值得评论。
In the Framework we are building we need the following pattern:
public class BaseRenderer
{
Func<string> renderer;
public BaseRenderer(Func<string> renderer)
{
this.renderer = renderer;
}
public string Render()
{
return renderer();
}
}
public class NameRenderer : BaseRenderer
{
public string Name{ get; set; }
public NameRenderer ()
: base(() =>this.Name)
{}
}
As you see, we create a lambda when calling the base constructor.
public class Program
{
public static void Main()
{
Console.WriteLine(new NameRenderer(){Name = "Foo"}.Render());
}
}
Oddly, when trying to actually use the lambda it throws NullReferenceException (Console Application), or some kind of ExecutionEngineExceptionexception (Web app on IIS).
I think the reason is that this pointer is not ready before calling base constructor, so the lambda is unable to capture this.Name
at this stage.
Shouldn't it throw an exception in "capture time" instead of "execution time"?
Is this behavior documented?
I can refactor the code in a different way, but I think it worths a comment.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
正如 asgerhallas 正确指出的那样,根据规范,这不应该是合法的。我们不小心让这种虚假用法潜入了错误检测器,该检测器在合法之前搜索“this”的不正确用法。我已经修复了这个错误; C# 4 编译器正确地将您的程序标记为错误。
对于给您带来的不便,深表歉意;这是我的错误。
As asgerhallas correctly points out, this should not be legal according to the specification. We accidentally allowed this bogus usage to sneak by the error detector that searches for incorrect usages of "this" before it is legal to do so. I've fixed the bug; the C# 4 compiler correctly flags your program as an error.
Many apologies for the inconvenience; this was my mistake.
C# 规范 7.5.7 规定:“仅在实例构造函数、实例方法或实例访问器的块中允许 this 访问。”
10.11.1 中更直接地说:“实例构造函数初始值设定项无法访问正在创建的实例。因此,在构造函数初始值设定项的参数表达式中引用 this 是一个编译时错误,因为它是一个编译时错误通过简单名称引用任何实例成员的参数表达式。”
虽然实例是按照7.5.10创建的。
唔。这实际上很奇怪。我没有看到任何编译时错误。
The C# specification at 7.5.7 says: "A this-access is permitted only in the block of an instance constructor, an instance method, or an instance accessor."
And even more directly in 10.11.1: "An instance constructor initializer cannot access the instance being created. Therefore it is a compile-time error to reference this in an argument expression of the constructor initializer, as is it a compile-time error for an argument expression to reference any instance member through a simple-name."
Though the instance has been created according to 7.5.10.
Hmm. That's actually pretty strange. I did not see any compile-time error.
我认为你是对的。当调用基类构造函数时,子类尚未构造,因此访问子类上的成员会给您一个空引用。 CLR 无法在编译时知道实例是否存在。
将逻辑移至构造函数主体应该可以解决问题。
I think you are right. The subclass is not yet constructed when the base class constructor is called and therefore accessing members on the subclass gives you a null reference. CLR doesn't have a way to know at compile time if the instance exists or not.
Moving the logic to the constructor body should fix the problem.
lambda 捕获了“this”的值并捕获了 null,因为该对象尚未构造。这让我觉得这是一个编译器错误,它应该为此生成一个错误。像这样的代码通常会生成 CS0027(关键字“this”在当前上下文中不可用)或 CS0120(需要对象引用)。我敢打赌这不容易实现。
无论如何,代码无法工作。 NameRenderer 类需要一个带有字符串参数的构造函数,以便它可以初始化基类。
The lambda captured the value of "this" and captured null since the object wasn't constructed yet. This strikes me as a compiler bug, it should have generated an error for this. Code like this normally generates a CS0027 (keyword 'this' is not available in the current context) or CS0120 (an object reference is required). I bet that isn't easy to implement.
Anyhoo, the code cannot work. The NameRenderer class needs a constructor with a string argument so it can initialize the base class.
: base(()=>this)
不合法吗?你可以做: this()
所以对 this 的引用似乎没问题,只是不是它的属性。: base(()=>this)
不再合法这一事实只是破坏了我在构建过程中所做的一些部分函数应用程序。可以通过将其移动到构造函数的主体中来修复,但存在顺序差异:基类不能再将部分函数应用程序透明地传递给自身(因为基类构造函数在子类构造函数的主体之前被调用) )。wouldn't
: base(()=>this)
be legal? You can do: this()
so a reference to this seems to be fine, just not properties on it. The fact that: base(()=>this)
is no longer legal just broke some partial function application I did during construction. Can be fixed by moving it into the body of the constructor, but there is an order difference: the base class can no longer be passed transparently a partial function application to itself (because the base class constructor gets called before the body of the subclass constructor).