Inheritance and Polymorphism

Inheritance and Polymorphism

Inheritance

In some circumstances, it is useful to extend the functionality or identity of one class to another. You might want to do this to make you code more reusable, quicker to write or more customisable. In programming, we call this concept Inheritance.

Inheritance as part of polymorphism

When you make a class inherit from another class, the functionality is reused, which generally means you do not have to write it from scratch.

As an example, we know that a horse is a type of mammal. We also know that a cat is a type of mammals, there are a number of field and methods that can be defined for a mammal and inherited by the horse and cat classes.


using System;

public class Mammal
{
	public string bloodType = "Warm Blooded";
	public bool hasHairOrFur = true;
}

public class Horse : Mammal
{

	public void makeNoise()
	{
		Console.WriteLine("Neigh");
	}

}

public class Program
{
	public static void Main()
	{
		Horse horse = new Horse();
		horse.makeNoise();
		Console.WriteLine(horse.bloodType);
	}
}

                

In this example, we can see that the Horse class inherits from the Mammal class, and due to this, the Horse class contains its own members, as well as the members from Mammal (that Horse has access to).

We call the class that inherits a subclass, and the class that is inherited from the superclass.

Polymorphism

The word Polymorphism means “To have many shapes or forms”. In this case, we apply this philosophy to inherited classes. This allows for a couple of things to happen.

When a subclass inherits from a superclass, the subclass can be treated as an object of the base class as it is guaranteed to have the same member fields and methods as a bare minimum thus ensuring compatibility.

A subclass can contain a definition for a virtual method implemented in the superclass, this will override the original function.

Polymorphism is useful in C# as it allows for a group of related objects to be treated in a similar fashion without having to adapt your code.

The C# documentation has a great code example of how Polymorphism is used in the context of shapes over at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/polymorphism

Implicit and Explicit Conversion to Inherited Types

In an earlier tutorial, we discussed how a type can be converted to another type. There were two main types of conversion:

  • Implicit conversion for when the target type can always accommodate the source.
  • Explicit conversion for when the target type may not be able to accommodate the source due to its size or precision.

A similar approach is taken with inherited types.

If you wish to convert a subclass to a base class then this is an implicit conversion as the compiler can do this automatically. This is referred to as Upcasting.

It will generally be the case that the superclass has a more restrictive view of the object than the subclass. This means that if you try to access a field or method defined in the subclass, you will receive an undefined error.

Downcasting is the opposite. Downcasting is the process of converting an instance of a superclass to an instance of a subclass.

Downcasting requires a cast operator, the new type in brackets, as the conversion can fail at runtime due to missing members.

The ‘as’ Operator and Inheritance

The as operator can be used when downcasting types. The as operator causes the result of the downcast to evaluate to null if it fails.

                    
                        using System;

public class Mammal
{
	public string bloodType = "Warm Blooded";
	public bool hasHairOrFur = true;
}

public class Horse : Mammal
{

	public void makeNoise()
	{
		Console.WriteLine("Neigh");
	}

}

public class Program
{
	public static void Main()
	{
		Mammal mammal = new Mammal();
		Horse horse = mammal as Horse;
	}
}
                    
                

This is useful as you may later want to check if the result is null before trying to access a member from the object.

The ‘is’ Operator and Inheritance

The is operator tests to see if a conversion between two references will succeed. It can determine if a subclass inherits from a particular superclass. It is a useful safety check before downcasting to a type.


                   using System;

public class Mammal
{
	public string bloodType = "Warm Blooded";
	public bool hasHairOrFur = true;
}

public class Horse : Mammal
{

	public void makeNoise()
	{
		Console.WriteLine("Neigh");
	}

}

public class Program
{
	public static void Main()
	{
		Horse horse = new Horse();
		Mammal mammal = new Mammal();
		Console.WriteLine(horse is Mammal);
	    Console.WriteLine(mammal is Horse);
	}
}
                

Virtual Members

The virtual keyword can be used as a modifier to specify methods, properties, indexers or events that are intended to be overwritten in a subclass as a specialised implementation.

To override a virtual member in a subclass the same names should be used when writing your class definition as well as using the keyword ‘override’ in methods. For example


                    class Base
{
    public virtual string Test1 { get; set; }
    private int testNum;
    public virtual int Number
    {
        get { return testNum; }
        set { testNum = value; }
    }
}

class SubClass : Base
{
    private string test1;

    public override string Test1
    {
        get
        {
            return test1;
        }
        set
        {
            if (!string.IsNullOrEmpty(value))
            {
                test1 = value;
            }
            else
            {
                test1 = "Unknown";
            }
        }
    }
}
                

The name of the method, the parameters and the accessibility must all be the same than that of the virtual method.

Abstract Classes

An abstract class is a type of class that cannot be instantiated, in other words, you cannot make an object with an abstract type. The purpose of abstract classes is to template the implementation of its subclasses which can be instantiated.

The difference between virtual and abstract implementations is that there is no default behaviour defined for the members of an abstract type whereas this is required for a virtual type.


                    using System;

public abstract class Mammal
{
	public abstract string bloodType {get; set;}
	public abstract bool hasHairOrFur {get; set;}

	public abstract void makeNoise();
}

public class Horse : Mammal
{
	public override string bloodType {get; set;} = "Warm Blooded";
	public override bool hasHairOrFur {get; set;} = true;

	public override void makeNoise()
	{
		Console.WriteLine("Neigh");
	}

}

public class Program
{
	public static void Main()
	{
		Horse horse = new Horse();
		Console.WriteLine(horse.bloodType);
		horse.makeNoise();
	}
}
                

Hidden Inherited Members

Sometimes subclasses are said to hide the members of their superclass if they have the same names. For example, if we have an int called test in both a superclass and its subclass, the subclasses version of test will hide test in the superclass.

When this happens, the compiler will warn the user of this occurrence. Sometimes hiding a member is done intentionally, if this is the case then the new keyword can be added to the member in the subclass. This does not change the code but instead indicates to the compiler that you meant to hide the superclass.

Sealed Keyword

A superclass can prevent subclasses from overriding the implementation of their members by using the sealed keyword.

If you were to use the sealed keyword in the class definition, this would prevent the class from being used as a base class.


                  using System;

public sealed class Mammal
{
}

public class Horse : Mammal
{

}

public class Program
{
	public static void Main()
	{
		Horse horse = new Horse();
	}
}
                

Sealing only prevents a member from being overridden, it will not prevent a member from being hidden.

Base

The ‘base’ keyword is similar to the this keyword, instead of accessing the current instance of a class, it will access members of the base class the current object inherits from.

This may be useful if methods of the base class have been overridden or if the constructor from the original base class is needed.

For more information on how this works at a lower level, please visit the Microsoft documentation:

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/base

Inheritance for Constructors

Although it is possible to access a base classes constructors from a subclass, they are not automatically inherited within the new type.


                    using System;

public class A
{
	public A(int test)
	{
		Console.WriteLine($"number {test}");
	}
}

public class B : A
{
}

public class Program
{
	public static void Main()
	{
		A a = new A(123);
		B b = new B(123);
	}
}
                

If no new constructor needs to be defined for the subclass then this can quickly be resolved using the base keyword.


                using System;

public class A
{
	public A(int test)
	{
		Console.WriteLine($"number {test}");
	}
}

public class B : A
{
	public B(int test) : base(test)
	{
	}
}

public class Program
{
	public static void Main()
	{
		A a = new A(123);
		B b = new B(123);
	}
}
            

The only exception to this case is when wanting to call a parameterless constructor to the base class, this will be implicitly called without issue.