简体   繁体   中英

Corner case in using lambdas expression in base constructor

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.

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.

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.

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.

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.

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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM