Why do we need to explicitly call parent constructor in MSIL?
Asked Answered
B

1

10

I just spent hours being confused by an NullReferenceException where I thought there shouldn't be one. I was constructing a class like so:

public class MyClass : MyBase<Foo>
{
    public MyClass()
    {
        base.Method(Foo.StaticField);
    }
}

where

public class MyBase<T>
{
    private SomeObject bar = new SomeObject();

    public void Method(object o)
    {
        this.bar.AnotherMethod(o); // exception thrown here
    }
}

Basically my IL was the following:

ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Ldsfld, staticField);
ctorIl.Emit(OpCodes.Box, typeof(FieldType));
ctorIl.Emit(OpCodes.Call, parentMethod);
ctorIl.Emit(OpCodes.Ret);

and I finally figured that it must be that bar was not being instantiated. I constructed my class in C# and compiled it and found the only difference was that the following should be above the IL above:

ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Call, parentCtor);
// as above

With these lines, my code now works as expected.

So my questions are:

  1. Why do we need to explicitly call the base constructor to instantiate fields?
  2. Is there a legitimate use of not calling the base constructor?
  3. ...and if not, why does the CLR accept it as a valid program?
Barimah answered 11/9, 2014 at 13:11 Comment(4)
@PatrickHofman Best edit ever :PBarimah
The base class constructor is always called. If you don't write it explicitly in your C# code then the compiler does it for you. And do note that there always is one, System.Object has a constructor. Very easy to see with ildasm.exe, the tool you always want to use first before you start generating your own MSIL.Piercing
@HansPassant in C# it is, yes; in IL: not so muchWo
Hmm, no, even C++ compilers generate unnecessary base constructor calls. It is the job of the code generator to eliminate them. The jitter does that well, it doesn't need any help. That the verifier doesn't require it is just a detail, the zero-initialization promise is enough to generate verifiable code. That NRE is guaranteed.Piercing
W
6
  1. because it isn't automatic, and it allows compilers and IL code to decide both when and if to call the base constructor
  2. well, you can actually instantiate types without using any constructors whatsoever... edge case, sure; but allowed
  3. because it is allowed

Note that you can actually emit a simple constructor (including base call) in a single method:

typeBuilder.DefineDefaultConstructor();
Wo answered 11/9, 2014 at 13:21 Comment(9)
Ah so DefineDefaultConstructor basically does the first two lines of IL that I've done here automagically?Barimah
@Barimah yep; from MSDN: "Defines the default constructor. The constructor defined here will simply call the default constructor of the parent."Wo
Should RTFM shouldn't I? :P Thanks Marc.Barimah
Out of interest, do you know any real-life examples of (2)?Barimah
@Barimah FormatterServices.GetUninitializedObject(Type) - kinda cheating, yesWo
@Barimah Yes, I do. It could be used by the Deserializers. You could get an instance without calling the constructor by calling FormatterServices.GetUnInitialzedObject then you immediately populate the fields.Confiding
Cheers Marc and Sriram.Barimah
Is it possible to do 2 without FormatterServices.GetUnInitialzedObject?Wherein
@Wherein if we include structs: sureWo

© 2022 - 2024 — McMap. All rights reserved.