Tuesday, April 30, 2024 16:06

Table of contents >> Object Oriented Programming > Inheritance

Inheritance

In the first lesson of the Objects chapter, I was discussing in a broad way about Object Oriented Programming, acronymed OOP, and I was enumerating it’s fundamental principles: encapsulation, inheritance, abstraction and polymorphism. In this lesson, I will explain inheritance at large, and how class hierarchies improve code readability and reusability.
Since OOP tries to emulate the real world made up of objects, inheritance is also a principle that does exactly what its name suggests: it allows an object (a class) to inherit properties or behaviors of another more broad object (class). A basic example of real world inheritance is that a lion inherits (is part of) a greater biological group, of cats (Felidae). This group also inherits from a broader group, of mammals. Mammals are part of (inherits from) an even more broad group, of animals, and so on.

Unlike C++, C# does not support multiple inheritance, meaning, you cannot have a class that inherits from both a class Human, and another, Scientist. This is because of the difficulty in deciding which methods to use, if both parent classes implement a method with same name (C++ solves this in a very complicated manner). However, C# does allow implementation of multiple interfaces, which can help emulate multiple inheritance. I will discuss this in a future lesson.

In programming jargon, the class from which we inherit is called parent class, base class, or super class. Here is a simple example of base class:

We have a boolean property called Male, and two constructors, one of which just calls the other, with a predefined value of True.

Now, we could have another class, called Cat, that inherits from Felidae class:

The first thing to notice is the syntax of inheritance: by placing a colon after the name of the child class, followed by the name of the base class, we are telling the compiler that we want to inherit it. In my example, Cat : Felidae means that the class Cat inherits the class Felidae. I used the keyword base in the constructor of the class Cat; this keyword indicates that the base class must be used and allows access to its methods, constructors and member variables. Using base(), we can call the constructor of the base class. Using base.Method() we can call a method of the base class, pass parameters to it and use its results. Using base.field we can get the value of a member variable from the base class or assign a different one to it.

As we will learn in the lesson about virtual methods, in .NET, methods inherited from the base class and declared as virtual can be overridden. This means changing their implementation; the original source code from the base class is ignored and new code takes its place.

As with the keyword this used in the context of class instantiation, which indicates that we refer to that particular instance of the class and which can be omitted if we don’t access a member of a different class with same name as another member of the instantiated class, we can invoke non-overridden methods from the base class without using the keyword base. Using the keyword is required only if we have an overridden method or variable with the same name in the inheriting class.

The keyword base can be used explicitly for clarity. base.method() calls a method, which must be from the base class. Such source code is easier to read, because we know where to look for the method in question. Bear in mind that in the context of inheritance, using the keyword this is not the same as using it in the context of instantiation. It can mean accessing a method from the instance, as well as the base class.

When inheriting from a base class, the constructor of the inheriting class must call the constructors of the base class, in order to initialize its member variables. We are not required to do this explicitly, because if we don’t, the compiler will automatically do it for us, with the default base constructor. Consider this example in which we inherit from a class, but we don’t call its constructor:

In reality, the compiler will transform our code to this:

which will inherently call the default, parameterless, constructor of the base class. In case this particular constructor is not declared, or it’s access modifiers won’t allow us to access it from the inheriting class, we must explicitly call another overloading constructor of the base class, or we will get a compiler error: Error CS0122 ‘BaseClass.BaseClass()’ is inaccessible due to its protection level:

If a class has private constructors only, then it cannot be inherited, and this could indicate many other things. For example, no-one (other than that class itself) can create instances of such a class. Actually, that’s how one of the most popular design patterns (singleton) is implemented.

Calling the constructor of a base class happens before the body of the constructor of the inheriting class is executed. This is because the fields of the base class should be initialized before we start initializing fields of the inheriting class, because they might depend on a base class field.

In the lesson about access modifiers, I have said that there are 4 main types of access modifiers: public, private, protected and internal, with an additional combination of protected internal. In the context of inheritance, I can now explain some of the concepts that at the time made little sense:

  • protected access modifier defines class members which are not visible to users of the class (those who instantiate, initialize and use it), but are visible to all inheriting classes (descendants). This means that these members are only accessible within the class they are declare in, and in child classes that inherit from the class they are declared in. They are not accessible in the instances of their class.
  • protected internal defines class members which are both internal (visible within the entire assembly), and protected (not visible outside the assembly, but visible to classes who inherit the class they are declared in, even outside the assembly).

Additionally:

  • all of its public, protected and protected internal members (methods, properties, etc.) are visible to the inheriting class.
  • all of its private methods, properties and member-variables are not visible to the inheriting class.
  • all of its internal members are visible to the inheriting class, only if the base class and the inheriting class are in the same assembly (the same Visual Studio project).

Example:

If we try to compile the above examples, we will get a compiler error, because the variable male is declared as private in the base class, and thus, it is only visible within that class.

In .NET, there is a base class called Object, System.Object or simply object, which is the base class from which all other types inherit (for instance, even int, bool, string inherit from object), directly or indirectly. In this light, all objects can be considered instances of this base class. All classes that do not inherit a class specifically, inherit from object (the compiler takes care of that). If they do inherit from some class, they inherit from object indirectly too, from it. This way, any class inherit directly or indirectly from object, with all its properties and methods. This also has the inherent ability to be able to cast any type into object, which is also known as upcasting.

Because any class inherit from object, we are allowed to assign the class instance cat of type Cat to a variable of type object, because Cat inherits from object. On the other hand, if we want to cast a variable of type object into a specific type, we need to use explicit type conversion. This is because any class can be an object, but not any object can be a certain class:

Since the object type variable obj could or could not be of type Cat, we need to place the conversion inside a Try…Catch block, in case the casting fails.

Object type has a method called ToString(), which converts the object to a textual representation. Since all classes inherit from object, this means that all classes inherit and have the method ToString(). However, since an object could contain multiple members (properties, methods, variables, etc), the compiler doesn’t know how to convert such an object to text. For this reason, if we use the default inherited implementation of ToString() method, the compiler will simply print the name of the object:

Default inherited ToString() method

If we want to alter this behavior, we need to override the default implementation of the ToString() inherited method:

You can notice that we declared a method named ToString(), and we also used the keyword override, because this method overrides the implicit one of the type object, from which Cat inherits indirectly. This way, whenever we will call the ToString() method on a Cat instance, the compiler will use our custom implementation of the method:

Override ToString() methodIf you want to declare a class that you don’t want to be inherited by any other class, you can use the keyword sealed in the class declaration, like so:

In the next lesson, I will write about virtual methods and why we used the override keyword in the above example. Until then, remember that base classes and inheritance can be used to apply a common behavior to multiple related objects.

Tags: , , , ,

Leave a Reply



Follow the white rabbit