The semantics of constructors (i.e. new methods) is different in X++ and other managed languages like C#. In X++ you are allowed to reference member variables in parent classes even before super() (or base() in C#) has been called to run the parent constructors. This is not possible in C#, for instance where the syntax forces a call to the base constructor (whether or not you actually provide it yourself or the compiler generates it for you) before any of the statements in the constructor are executed. Let me illustrate with an X++ example:
class BaseClass
{
int b;
void new() {}
}
class DerivedClass extends BaseClass
{
int d;
void new (int v)
{ b = 33;
super();
d = 8;
}
}
As you can see, the derived class initializes the b member variable before super() is called. I C# this cannot happen, since you are required to call :base() prior to all statements in the constructor. This particular example is a little contrived, to illustrate a point - Obviously you would want to initialize the member variables in the class that defines them (initializing b in class B) as a matter of good practice.
To make this work (and this is a carryover from the old interpreter days) there is a difference between the actual construction of the object and the constructor method (i.e. the new method) – In X++ these are two different things. Please consider:
To understand this better it is instructive to look at the code generated for the example above:
[DebuggerHidden]
protected DerivedClass() : base()
{
}
public DerivedClass(int num) : base()
{
if (this.__shouldCallNew(typeof(DerivedClass)))
{
this.new(num);
}
}
[DebuggerStepThrough]
protected override bool __shouldCallNew(Type type)
{
return (object)type == (object)typeof(DerivedClass);
}
The X++ compiler places a [DebuggerHidden] attribute so the user does not get distracted by stepping into things that may be confusing. We want the user to perceive the semantics of the new method as the constructor, even though that is an indirect link.
We will dig into this a little deeper below, but first notice that the code to instantiate an instance of the type DerivedClass is the normal IL code that you would find in any other language. We cannot make any compromises here, since it must be possible to create instances of X++ classes from C#, that would not know to put in any special boilerplate code. So in X++
new DerivedClass(4)
is translated into the obvious MSIL:
new DerivedClass(4):
so there is no surprise there.
Now let's walk through this construction of an instance of DerivedClass:
The second constructor
[DebuggerHidden]
protected DerivedClass() : base()
{
}
public DerivedClass(int num) : base()
{
if (this.__shouldCallNew(typeof(DerivedClass)))
{
this.new(num);
}
}
calls this.new(num)
which will call the user's new method. The _shouldCallNew(typeof(DerivedClass) is needed to avoid the local new method being called if the constructor was called from a derived class. The super() calls in the constructor are handled from the new method that we show here (edited for clarity). The super() call is translated to a base.new() call as you might expect.
public new void new(int i)
{
this.b = 33;
base.new(i);
this.d = 8;
}
Here we are calling the base new method (not the base constructor). Note how we generate a parameter-less constructor as well:
[DebuggerHidden]
protected DerivedClass() : base()
{
}
This is what is called to satisfy the implicit super calls of the real constructors. It this were not done, the code would not load at runtime since it would not fulfill the guarantee that all objects are fully initialized before their member variables are accessed that .NET requires.
It is useful to peruse the exact MSIL code generated for this parameterless constructor:
.method family specialname rtspecialname instance void .ctor () cil managed
{
.custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = (
01 00 00 00
)
IL_0000: ldarg.0 // The this pointer
IL_0001: call instance void [Dynamics.AX.ApplicationFoundationUnitTests]Dynamics.AX.Application.BaseClass::.ctor()
…
IL_000c: ret
}
As you can see, there is a call to the type that the X++ compiler knows is the parent class of the Derived class, i.e. BaseClass (as highlighted above). There is a consequence that is now obvious: This makes it illegal for use to create a new parameterless constructor of your own between ISV code and our code. The ISV code would still refer to the base class it knows from compiletime so the newly introduced default constructor would not be called by the ISV code, before that ISV code is recompiled. When ever we release our code, we have checks against a baseline that prohibit adding such parameterless constructors.
That is it for constructors for this time. I hope it was instructive.
*This post is locked for comments