简体   繁体   中英

Why can't a member method be passed to a base class constructor?

class Flarg
{
    private readonly Action speak;

    public Action Speak
    {
        get
        {
            return speak;
        }
    }

    public Flarg(Action speak)
    {
        this.speak = speak;
    }
}

class MuteFlarg : Flarg
{
    public MuteFlarg() : base(GiveDumbLook)
    {
    }

    private void GiveDumbLook()
    {
    }
}

The compiler gives an error "An object is required for the non-static field, method, or property 'Project.Namespace.Class.GiveDumbLook'.

This seems no different than passing an action as a parameter to any other method. Why is this invalid?

Edit Great answers. Thanks to everyone. I guess this just confuses me, because it seems like it is the opposite-side-of-the-coin from this question ; where the highest voted answer clearly states

AC# object is fully constructed and initialized to zero before the first constructor runs.

By that statement, it seems that the above code should work. Apparently there is a subtle difference.

Rewrite it like this:

public MuteFlarg() : base(this.GiveDumbLook) { }

and it is now clear why you can't. It's not legal to refer to this in a base class constructor invocation. This is not legal because it can easily lead to bugs. The constructor for the derived class has not run yet, and so the fields are not set to their initial state (initial state being defined by the state of the object when the constructor is done running).

This is explicitly stated in §10.11.1 of the specification:

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 .

The last statement explicitly forbids referring to this.GiveDumbLook by its simple-name GiveDumbLook .

I just want to make sure that a subtle point is clear here.

As Jason correctly notes, it is not legal in C# to access "this" or any member of "this", either implicitly or explicitly, before the constructor body runs. All field initializers (even those on base classes) run before any constructor body, so it is not legal to use "this" in any field initializer. Similarly, the "base()" or "this()" constructor initializer clause runs before a constructor body, and so it is not legal to access "this" or a member of "this" in an argument to the constructor initializer.

The reason why we have this behaviour is because accessing "this" before the body runs is a bad programming practice. It is highly likely to result in there being ordering dependencies in field initialization. It is highly likely to result in "readonly" fields being accidentally observed to be in their uninitialized state. It is highly likely to result in virtual methods calls to more derived methods that depend on as-yet-uninitialized state to run correctly. All these things make bugs, and C# is supposed to be a language that prevents bugs by design. Basically, we don't want you to touch "this" until you're in a method body and can perform more advanced logic than mere assignment to fields.

It is not the case that it is impossible to access "this" because "the base object is not created yet" or some such thing. Before any part of the constructor or field initializers begin to run, the object is guaranteed to be fully created and initialized to its default state. The "this" object certainly exists at the point where the field initializers are running -- it has to exist, because the field initializers are modifying it!

In short: we don't let you touch "this" because doing so is error-prone, not because doing so is impossible.

In this specific case you're passing a delegate to a method to a base constructor, which could invoke that delegate, thereby invoking a method that might depend on state that has not been created yet, because the more derived constructor body has not run yet.

Only static fields could be passed to the base constructor. The object is not yet initialized so you cannot use instance members. If you make the GiveDumbLook static it will work.

Remember that a delegate consists of two parts:

  • a method to call
  • a target instance

When calling the base constructor, the method is know, but the target instance isn't yet constructed.

So, you can omit the target instance, creating an open delegate. The easiest way to do this is using a lambda:

class Flarg
{
    private readonly Action speak;

    public Action Speak
    {
        get
        {
            return this.speak;
        }
    }

    public Flarg(Action<Flarg> speak)
    {
        this.speak = () => speak(this);
    }
}

class MuteFlarg : Flarg
{
    public MuteFlarg() : base(x => ((MuteFlarg)x).GiveDumbLook())
    {
    }

    private void GiveDumbLook()
    {
    }
}

因为在基类( Flarg )构造的阶段,您没有MuteFlarg类的实例,因此无法访问非静态方法或字段。

如果尚未构造对象,如何传递非静态成员?

the object doesnt exist yet at the moment of the creation of the base object. so how should it already make a "pointer" to the method?

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