다음을 통해 공유


Anders on virtual calls from constructors in C# and .NET

Years ago, I had the good fortune to work with Eric Lippert on VBScript and JScript.  I was a young program manager and he was an even younger developer who initially set himself apart by always wearing a white hat.  Maybe the subliminal message was that he was a good guy?  Well, subliminal message or not, that is definitely the case.  Eric sent me email a few weeks ago with an interesting design question.  Despite being involved in all of the design discussions on the issue, I couldn't remember the answer.  Anders could, and I provide the q & a below.

 

<question>

I just had an interesting conversation that exposed what looks to me like a design flaw in C#.  I thought I might run it by you, if you don't mind, to see if you can explain to me that there really is a good reason for this.

 

Here's the deal.  Consider a C++ derived class which is destructed.  The derived class destructor runs, then the base class destructor runs.  When a base class destructor calls a virtual method, the compiler ensures that the base class's version of the virtual method is called, rather than the derived class's version. 

 

This seems initially odd, but makes sense -- the derived class's implementation almost certainly relies upon state which has just been destructed, so you shouldn't call it after the derived destructor runs.

 

Now consider page 346 of "The C# Programming Language", where you point out that a base class constructor can call a virtual method on a derived class before the state of the derived class is fully initialized.

 

It came up because one of our testers has the following code:

public

class Project // base class owned by another team, we can't change it easily
{
public Project()
  {
this.ShowProjectUI();
}

  public virtual void ShowProjectUI() { }
}

public

class TrinityProject : Project // our code
{
private bool dismissAsserts;

  public TrinityProject(bool dismiss): base()
  {
    this.dismissAsserts = dismiss;
}

  public override void ShowProjectUI()
  {
    if (this.dismissAsserts)
      CreateDismisserThread();
  }
}

Since the base class constructor, and hence the virtual method, runs before the derived class constructor, the dismiss asserts member is only ever set to its default value when the initial UI is created.  The caller's parameter is essentially ignored.

 

I would expect C# to either (a) call the base class method if the derived class is not fully constructed (which seems bad), or (b) provide a syntax for initializing derived class members using constructor arguments before base class constructors run (which seems good, and is what C++ does).

 

My two questions to you are then:

  • what's up with this?  Is there some goodness here that I'm missing out on?  Because this seems like a big old gotcha waiting to happen, calling virtual methods before the object is constructed.
  • any thoughts on how my tester buddy Ian can make his derived class work without petitioning the owner of the base class to stop calling virtual methods from the constructor?

Thanks!

 

Eric

</question>

 

<answer>

We (C#, .NET guidelines, FxCop) discourage calling virtual methods from constructors. Forbidding it outright would be hard to do--even if we only allowed constructors to call non-virtual methods, those methods could in turn call virtuals. Also, it is occasionally useful.

 

C++ takes an object through a progression of type identities as a constructor executes--basically, each constructor updates the v-table after its call to the base constructor returns. As a result, virtual calls in a constructor never go deeper than the constructor's level of inheritance. This scheme provides a certain amount of protection against observing uninitialized instance variables in virtual methods, but I don't think it is a particularly useful or intuitive model. Indeed, the notion that an object goes through a progression of base types until it finally reaches its true type (and vice versa on destruction) is rather odd, and you could even argue incorrect.

 

Unlike C++, C# (or, more correctly, .NET) immediately assigns a newly allocated object its true type. Thus, virtual call dispatch is consistent across the entire lifetime of the object, but it is possible to receive virtual calls before execution reaches a constructor body. The big difference between C++ and .NET is that .NET always zeroes out the memory of a newly allocated object before executing any constructor code. So, while virtual methods may observe the zeroed out state of instance variables, they will never observe an undefined state and type safety is not compromised.

 

BTW, .NET's behavior is consistent with most other OOP systems, Java included. In fact, I don't know of any language but C++ that uses the "multiple personalities" approach to object construction.

 

Anders

</answer>

 

Though I'm several management layers away from the daily work of language design, I still enjoy thinking about issues like this one.

 

--Scott

Comments

  • Anonymous
    January 14, 2005
    The comment has been removed

  • Anonymous
    January 14, 2005
    No, you can't do that.

    According to 10.10.1 in the language reference: "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."

    --Scott

  • Anonymous
    January 14, 2005
    While the code is technically correct, Eric may have found a logic bug after all.

    If you instantiate TrinityProject with a parameter of true, the ShowProjectUI override is called during construction -- but at this point, the instance field dismissAsserts is still false because it has not yet been set!

    To me, this code looks like the TrinityProject ctor should repeat the call ShowProjectUI after dismissAsserts has been set. If this is not the intention, a clarifying comment might be in order ("set field for ShowProjectUI calls after construction").

  • Anonymous
    January 14, 2005
    In the Delphi (Object Pascal) language that Anders worked on originally, you have the same behavior as in C#... except that you have to call the base constructor explicitly.

    constructor TTrinityProject.Create(Dismiss: Boolean);
    begin
    FDismiss := Dismiss;
    inherited Create;
    end;

    procedure TTrinityProject.ShowProjectUI;
    begin
    // FDismiss is already set, even when called from the inherited constructor.
    ...
    end;

    I think this is the best solution. You can call a derived virtual method from a constructor which is often useful, and you have full control over when the inherited constructor runs. The same applies to destructors BTW.

  • Anonymous
    January 15, 2005
    I agree that the Delphi way looks like the best solution. I wonder why Microsoft dropped this feature in C#?

  • Anonymous
    January 15, 2005
    If this is a problem to be solved, then it is a problem with dynamic dispatch and how it is interpreted inside and outside a module.

    It seems to me there are a whole set of dynamic dispatch 'gotchas' having to do with module scope. I had the unpleasant luck to find one of my own in Java. (http://arcavia.com/kyle/Analysis/DynamicDispatchProblem.html)

  • Anonymous
    January 16, 2005
    "Unlike C++, C# (or, more correctly, .NET) immediately assigns a newly allocated object its true type. "

    I don't agree that this is true.

    Oh, the block of memory might be big enough to be the newly allocated type. And if you ask the system, "what's the type of this block of memory", it will tell you the newly allocated type.

    But until all the constructors have run, it's not an object of that type. It's something about to be of that type.

  • Anonymous
    January 16, 2005
    What do you mean, you "don't agree"? You know something about .NET that we don't?

    .NET isn't C++. The system provides well-defined initial default values for all data fields as soon as managed memory has been allocated. If inline initializers are present, their values are used instead. The ctors then may change any data fields as desired, but that's completely optional.

    Therefore, yes, you do have an object of that type. Except that some data fields don't have their final post-construction values yet.

  • Anonymous
    January 16, 2005
    You can half get around this behaviour by taking advantage of the fact that specific field initialisers are called before the constructor (or base constructor) is called. This is a documented aspect of the C# language.

    As a general rule I avoid calling virtual functions from a constructor, although I recognise that in this case the developer has less control over the base class. As this discussion shows, messing with the boundaries of the language throws up more questions than answers.

  • Anonymous
    January 17, 2005
    James: Does that mean you can do something like:

    object dummy = Initialize();
    private object Initialize() {
    // do whatever here
    }

    and get around the limitations?

  • Anonymous
    January 17, 2005
    The comment has been removed

  • Anonymous
    January 17, 2005
    Stuart Ballard wants to know if one can set a field by calling a method.

    The answer is that you can BUT the method MUST be static. Attempting to initialise a field by calling an instance method is a compile time error.

  • Anonymous
    January 22, 2005
    Strangely enough I had this exact problem at work this week, which set me wondering what the 'best practice' solution is.

    After all there are instances where it might be desirable for subclasses to override some setting, but you need to reference the setting in the (base) constructor for part of the object's core initialization.

    Deferring the virtual-referencing code till some kind of Init() method just raises more issues. Do you require the derived class to call Init() in its constructor (what if it forgets to), or the client (yuk). And if the former, what happens if the derived class is further subclassed, and that class wants to override the property too.

    The obvious answer would be to be able to call base() explicitly at somepoint in your constructor, rather than have it always run first, but I'm sure that raises a whole shed load of other issues, which

    Seems to me that the sequence issues in object construction is where polymorphism falls down a bit.

  • Anonymous
    January 26, 2005
    Trackback

  • Anonymous
    January 26, 2005
    The Getting Started with C++ section gives advice about learning C++. Learn from our C++
    tutorials, or test your programming knowledge with the C++ MegaQuiz. You can subscribe to Code Journal, a free biweekly programming newsletter.

  • Anonymous
    January 28, 2005
    People interested in the theory and practice of initialization may be interested in my paper "An Alternative Approach to Initializing Mutually Referential Objects", available at http://research.microsoft.com/users/dsyme/publications.aspx. I'll put an entry on my blog about it. Hope you enjoy it! If you have interesting examples of initialization puzzles then please let me know.

  • Anonymous
    June 30, 2006
    When growing up from time to time when I gave my name to someone I was often asked if I had any any relation...

  • Anonymous
    August 17, 2006
    When growing up from time to time when I gave my name to someone I was often asked if I had any any relation

  • Anonymous
    October 10, 2007
    One of the truly great things about working here is that Distinguished Engineers take time out of their

  • Anonymous
    February 17, 2008
    Favorites Build Providers for Windows Forms Curso del.icio.us-adev Dr. Dobbs Web 2.0 and the Engineering

  • Anonymous
    June 02, 2009
    PingBack from http://woodtvstand.info/story.php?id=86767

  • Anonymous
    June 15, 2009
    PingBack from http://workfromhomecareer.info/story.php?id=8979

  • Anonymous
    June 17, 2009
    PingBack from http://patioumbrellasource.info/story.php?id=297