Deep C# - Inheritance
Written by MIke James   
Monday, 27 December 2021
Article Index
Deep C# - Inheritance
Inherited Constructors
Is and As

Inherited Constructors

A special case of using the methods of the base class is the inherited constructor. A constructor is a function that creates the class and optionally initializes it. Beginners are often worried by the idea that the constructor has to “create” the class and they think that there is something they have to do to make it happen. In practice you don’t have to do anything to create an instance of the class. The system does it all for you and the constructor returns a reference to a new object with all of the properties the class defines initialized to their default values. However, if you want those properties to be initialized to something then you might have to write some code in the constructor.

If you want to provide different types of initialization then you can define a number of alternative constructors, each with its own unique signature. Which constructor is used is determined by the signature of the call. All classes have one constructor with no parameters, either explicitly defined or automatically generated, which is used as the default constructor in situations where you don’t explicitly specify a constructor.

If a class inherits from another class then the base class default constructor is automatically called before the child class’s own default constructor. If the base class also inherits from another class its constructor is called first and so on until we reach the first class in the inheritance chain.

If a class defines an explicit constructor and inherits from another class, the explicit constructor must call one of the base class’s constructors. If the base class only has constructors that use parameters, the child class has to call one of them using the base keyword.

For example, suppose MyClassA has a constructor of the form:

MyClassA(int n)

then to call it you would write:

class MyClassB: MyClassA
  rest of constructor

In this case the MyClassB constructor calls the MyClassA(int n) constructor as MyClassA(7) before continuing with its own instructions.

Notice that the this keyword can nearly always be used to make clear that the version of something to be used is the class’s own version and base can be used to indicate that the, possibly hidden, base class’s version should be used. In most cases you don't have to use this and base because the compiler can work out what you mean, but it is a good idea to be explicit whenever clarity warrants it.


Changing Types - Casting

As we already know, inheritance results in a type hierarchy of base classes and derived classes. We also know, see Chapter 2, that it is assumed that any derived class can stand in for its base classes, i.e. the classes it inherits from. This is such an established idea that we don’t think twice about the way that a variable can be used to reference any object of its type or derived from its type. So, in:

Class MyClassA{};

MyClassB inherits from MyClassA. Following on with:

MyClassA MyA;
MyClassB MyB;
MyA = new

you can see that you can use a reference to a base class to reference a derived class without any change in syntax.

This is called downcasting because the variable is supposed to reference a class higher up in the hierarchy but it actually references an object lower in the hierarchy.

It is also true that a variable of a derived type can reference a base type, but in this case we do need some extra syntax. For example:

MyB = (MyClassB) MyA;

In this case we need an explicit typecast expression, or just cast, to indicate the conversion, i.e. (MyClassB). Typecasts are simply types written in parentheses in front of a variable and they have the effect of changing the type of the object for the duration of the expression.

This is usually called upcasting because the variable is supposed to reference a class lower in the hierarchy than the object being assigned to it.

Upcasting may be legal but what does it mean?

The only sense that:

MyB = (MyClassB) MyA;

can possibly make is if MyA actually references an object of type MyClassB i.e. it has been downcast earlier. Only then can we make use of methods that are only defined on a type MyClassB object. That is:

MyB = (MyClassB) MyA;

only works if the object we are using, i.e. what MyA references, really is a type MyClassB object. If it isn’t then we are trying to call a method that doesn’t exist and the result is a run-time error.

You can see that upcasting isn’t type safe as you cannot easily work out whether what is being cast is of the correct type. For example:

Random R = new Random();
MyClassB myObject;
if (R.NextDouble() > 0.5) {
myObject = new MyClassB();
} else {
myObject = (MyClassB) new MyClassA();

This program generates no compile-time errors or warnings, but 50% of the time it fails because trying to cast an object of type MyClassA to a type MyClassB doesn’t work. In this case the random input is a stand-in for a variability injected into the program by the rest of the world in a real program.

Last Updated ( Saturday, 01 January 2022 )