SWeko's .net ramblings


1 Comment

The constructor that wasn’t there

Breaking changes are bad, and often it’s hard to convince ourselves that we really need them. However, it’s even worse when we do something innocent, that should not affect anything and we see that two hundred unit tests fail. Or even more fun for everyone, when everything seems fine, but a few days later, another developer comes, frothing at the mouth, yelling that he just wasted half a day because of our, obviously not-so-innocent, change.

A whole class of these problems, especially in object-oriented languages is connected to the “brittle base class” problem. Essentially it means that it’s never ever safe to change not only the interface, but even the behavior of the base class, because, someone, somewhere, depends on that behavior.
An example of this problem, specific to C# is connected with the default class constructor.

In general, the process of constructing an instance in .net consists of two steps, allocation of memory for the new object, and running a construction method on it. By definition, in order for a instance of any class to be constructed, it must have a constructor method.
The first step is done automatically for us, by the framework, and the second one is where we can do our work. In fact, the specification of the CLI, section I.8.9.6.6 in particular, insists that some constructor must be called.
In contrast to this, the C# language allows us to define a class without a constructor, i.e. the following are perfectly valid class definitions:

class Base {} // a class with no fields, properties, methods, or constructors
class Derived:Base {} // a derived class with no constructors
//and I can make new instances from those classes in the regular way
Base binstance = new Base();
Derived dinstance = new Derived();

So I can use those classes just like if I defined a constructor that takes no parameters. Actually, this is exactly what happens under the covers. The C# compiler detects that we have not provided a constructor for our class, so it provides a constructor for us. If we decompile the generated IL code, our base class will look somewhat like this:

class Base{
  public Base(){} //this constructor is generated automatically
}

And this is a great feature of C#, that we can just code away at the important stuff, without writing unnecessary code that can be inserted automatically by a machine.

However, the minute we add an instance constructor to our class, the compiler will not add any generated constructors to our class.1 This is a bit counter-intuitive, and in certain scenarios this could lead to unexpected errors, as usually adding independent code to a class should not break it, but it’s a design decision of the C# team, and we should live with it.

When we throw derived classes in the mix, the situation gets a little more complicated. According to section III.1.8.1.4 of the CLI specification, a constructor for a derived class must eventually call a constructor for the base class, i.e. no object can be constructed, without constructing a base class object2. Again, the C# standard makes is less cumbersome, by doing some of the plumbing work for us, in this case, injecting a call to the default (parameterless) base constructor. E.g. these two derived classes

class Derived1 : Base {}
class Derived2 : Base
{
  public Derived2 (int value){}
}

will be transformed to:

class Derived1 : Base
{
  //automatically generated constructor with an automatic base class constructor call
  public Derived1() : base() {} 
}

class Derived2 : Base
{
  //automatically added base class constructor call
  public Derived2 (int value) : base() {} 
}

This is great, as it ensures that the object will be properly constructed, and the compiler will not pester us with errors. That is, until we go in and add an explicit constructor to the base class.

class Base {
  public Base(string name){}
}

When we do that, the compiler does not automatically create a parameterless constructor, and the derived classes end up calling a non-existent constructor. The whole thing fails with the error: ‘Base’ does not contain a constructor that takes 0 arguments

The interesting part is that this error is issued on the line where the derived class is defined, because, as far as the base class is concerned, there are no errors. So, if the hierarchy is more than a couple of classes long, this message is very confusing to see.

There are two ways out of this. One is to explicitly call the new constructor from the derived classes – maybe adding a parameter to it’s constructor, maybe hard-coding a meaningful value, and the other is to add an explicit parameterless constructor to the base class. It’s either this:

class Derived1 : Base {
  //hard–coded and hopefully meaningful value
  public Derived1 : base(string.Empty) {}
}
class Derived2 : Base {
  //additional constructor parameters = more changes in code that uses this class
  public Derived2 (int value, string name) : base(name) {}
}

or this:

class Base {
  public Base(string name){}
  //calls the parameterful constructor with a hard-coded value
  public Base() : this(string.Empty) {}
}

The first solution is most likely the one we need, because if we added a parameter, its value is obviously needed by the class.

However, this will most likely be a breaking change not only to the derived classes themselves, but to the code that actually uses, or maybe even derives, the derived classes. So, if we do not have complete control of the derivation, i.e. if they are developed by another team, or if our base class is part of some library, we’ll have to take the second route, even if it means that some value is hardcoded to (hopefully) a sane default.

The lesson to draw from this is that designing a class hierarchy is hard. When we define a class that can be derived from, we are making a promise on both its contract (members and method signatures etc) and its behavior. We should always be vary when we introduce changes, and we should always make sure what and how they can break. If we do not want to make such a promise, we should declare the class as sealed, and then we know that the behavior we define is the behavior the consumer will get.

– inspired by http://stackoverflow.com/questions/12138221/does-not-contain-a-constructor-that-takes-0-arguments, feel free to upvote my answer 🙂