Abstract Classes vs. Concrete Classes in C#
The Concept of Abstraction
Imagine that someone posted the following classified ad for pet adoption:
Animal for adoption: $80
Naturally, the very first question you would ask is what kind of animal is up for adoption. Why is this so natural? Because there are only specific types of animals and there is no such thing as a general animal. However, all animals share certain characteristics. For example, all animals have a form of locomotion, make noises, and eat. These shared characteristics form the general aspects of an animal and don’t refer to any one specific animal type. This is the basic idea behind the concept of abstraction.
The Concept of Concreteness
All animals share certain functionalities, but each type of animal has its own unique way of performing those functionalities. For example, with locomotion, a human walks on two legs, but a snake slides on the ground. So, each type of animal is a concrete representation of the abstract definition of an animal. This is the essential relationship between abstractness and concreteness.
Abstract and Concrete Classes in C#
Abstract and concrete classes in programming are modeled after the concepts discussed above. For example, an abstract class forms the conceptual framework for classes that derive from it.
In addition, an abstract class, like an Animal, must be decorated with the abstract
keyword. By doing this, we are telling the compiler that this class cannot be instantiated as an object; it can only be derived or inherited by child classes that represent concrete examples of animals.
As explained in the analogy, concrete examples of animals implement their own unique mechanisms with each common feature. In C#, each common feature in the abstract class is a non-implemented method decorated with the abstract
keyword, and the corresponding, overridden methods in the child classes will have the unique implementation details. Take a look at the Animal class below:
To reiterate what was discussed so far, the bullet points below lists the basic features and rules of abstract classes:
Features of abstract classes in C#:
- An abstract class cannot be instantiated. It can only be inherited.
- An abstract class may contain abstract methods and properties.
- It is not possible to modify an abstract class with the sealed modifier because abstract and sealed modifiers have opposite meanings. The sealed modifier prevents a class from being inherited and the abstract modifier requires a class to be inherited.
- A concrete class derived from an abstract class must include actual implementations of all inherited abstract methods and properties.
Rules governing methods that are marked as abstract in an abstract class:
- An abstract method is implicitly a virtual method, which means it can be overridden by the derived subclass with a specialized implementation.
- Abstract method declarations are only permitted in abstract classes.
- Because an abstract method declaration provides no actual implementation, there is no method body; the method declaration simply ends with a semicolon and there are no curly braces following the signature.
Advantages of Using Abstract Classes in C#
It prevents logical errors
As an example, let’s examine a simple scenario:
A company consists of only specific types of employees. There is no such thing as a general employee. Imagine that a developer creates specialized concrete Employee
classes called Accountant
and Manager
, and suppose that these concrete classes inherit from an abstract class called Employee
. If the abstract Employee
class is itself a concrete class, there is nothing stopping other developers from instantiating this class, but it would make no sense to treat a general employee as someone that exists. To avoid this logical error, classes that describe a general, abstract concept like the Employee
class are decorated with the abstract
keyword.
Polymorphism can be Achieved with Abstract Classes
First, let’s break apart the term Polymorphism. Poly refers to many and morphism refers to form. So Polymorphism is a general term referring to many forms.
In programming, a construct like a class or a method can take on behaviors of Polymorphism. For example, a method in a parent abstract class can take on many forms if its child classes override that method and provide their own unique implementations.
At run time, when we invoke a method off of the child class object, the runtime engine will call the overridden method instead of the corresponding abstract method in the abstract class. Let’s illustrate this in the Car
class below:
As you can see in the code snippet, both the CombustionCar
and ElectricCar
child classes override the abstract StartEngine()
and Accelerate()
methods in the Car
abstract class and provide their own unique implementations.
In the code above, polymorphic behavior is achieved in the foreach
loop. Specifically, each concrete vehicle
object corresponding to the object in the cars
array is assigned to the abstract Car
type. As a result, at compile time, all that the foreach loop knows is that each object retrieved from the cars
array is of type Car
.
But at run time, the foreach loop can call the correct overridden method on either the CombusionCar
or the ElectricCar
child class depending on which object is passed in at each iteration of the loop. In this way, the abstract Car
class’s abstract StartEngine()
and Accelerate()
methods exhibit Polymorphism.
Abstract classes provide a layer of abstraction that achieves flexibility in your code. In other words, at runtime, we can swap concrete objects without the code knowing what type of object is being passed. All it cares about is that they are each a type of the abstract class. Creating abstractions and using Polymorphism in this way is an important element in some of the software design principles that embrace OOP.
Abstract classes can contain non-abstract properties, fields, and methods that are directly inherited
Finally, abstract classes provide the advantage of preventing code duplication by defining properties, fields, and methods that are shared and directly inherited by their derived child classes. These class members are called default implementations. Note that these class members will have an implementation body and will not be decorated with the abstract keyword.
The abstract Animal
class below illustrates these concepts:
Conclusion
I hope you found this article useful. Don’t forget to clap and follow me for more great articles!
Happy Coding!