Many developers have heard about abstract classes, but their implementation is a mystery. How can you as a developer identify an abstract class and decide when to use one? The definition is quite a simple one actually. Once you understand this fundamental definition of an abstract class, when and why to use one becomes obvious.
Imagine for a moment that you are developing an application that manages the animals in a cat sanctuary. The cat sanctuary rehabilitates lions, tigers, jaguars, leopards, cheetahs, pumas, and even domestic cats. The common noun that describes all these animals is the word cat. You can, therefore, safely assume that the abstraction of all these animals is a cat, and thus, this word identifies our abstract class. You would then create an abstract class called Cat
.
However, you need to keep in mind that you will never ever create an instance of the abstract class Cat
. All the classes that inherit from the abstract class also share some functionality. This means that you will create a Lion
class and a Tiger
class that inherit from the abstract class Cat
. In other words, the inherited classes are a kind of cat. Both classes share functionality in the form of Sleep()
, Eat()
, Hunt()
, and various other methods. In this way, we can ensure that inherited classes all contain this common functionality.
Let's go ahead and create our abstract class for cat. We will then use it to inherit from and create other objects to define different types of cats.
Chapter2
:Chapter2
is added to your solution. Go ahead and right-click on the default class called Class1.cs
that was added to your Chapter2
project and rename it to Recipes.cs
:Recipes
and that it exists in the Chapter2
namespace:namespace Chapter2 { public class Recipes { } }
Recipes
to an abstract class called Cat
. To do this, add the abstract
keyword to the class and change the name from Recipes
to Cat
. We are now ready to describe the Cat
abstract class:namespace Chapter2 { public abstract class Cat { } }
The abstract
keyword indicates to us that the object it is applied to has no implementation. When used in a class declaration, it basically tells the compiler that the class is to be used as a base class. This means that no instance of the class can be created. The only way in which implementation of the abstract class happens is when it is implemented by derived classes that inherit from the base class.
Eat()
, Hunt()
, and Sleep()
. You will notice that these methods don't contain a body (curly braces). This is because they have been defined as abstract. As with abstract classes, the abstract methods contained within the abstract class contain no implementation. These three methods basically describe functionality that is common to all cats. All cats must eat, hunt, and sleep. Therefore, to ensure that all classes that inherit from the Cat
abstract class contain this functionality, it is added to the abstract class. These methods are then implemented in the derived classes, which we will see in the upcoming steps:public abstract class Cat { public abstract void Eat(); public abstract void Hunt(); public abstract void Sleep(); }
Lion
class:public class Lion { }
Lion
class is simply an ordinary class and does not contain any common functionality defined in the Cat
abstract class. To inherit from the Cat
abstract class, we need to add : Cat
after the Lion
class name. The colon indicates that the Lion
class inherits from the Cat
abstract class. The Lion
class is therefore a derived class of the Cat
abstract class:public class Lion : Cat { }
As soon as you specify that the Lion
class inherits from the Cat
class, Visual Studio will show you an error. This is expected, because we have told the compiler that the Lion
class needs to inherit all the features of the Cat
abstract class, but we have not actually added these features to the Lion
class. The derived class is said to override the methods in the abstract class, and needs to specifically be written with the override
keyword.
Lion
class, Visual Studio will offer an explanation for the error via the lightbulb feature. As you can see, Visual Studio is telling you that while you have defined the class to be inheriting from the abstract class, you have not implemented any of the abstract members of the Cat
class:You can, therefore, see that using abstract classes is a fantastic way to enforce specific functionality within your system. If you define abstract members in an abstract class, the derived classes that inherit from that abstract class must implement those members; otherwise, your code will not compile. This can be used to enforce standards and practices adopted by your company, or to simply allow other developers to implement certain best practices as they use your base class for their derived classes. With the advent of the Visual Studio 2015 feature code analyzers, this can ensure a consistent development effort by the team.
Lion
class name and hit Ctrl + . (period). You can also click on the Show potential fixes link in the lightbulb popup. Visual Studio will give you a small heads up, displaying the changes it will make to your code. You can preview these changes by clicking on the Preview changes link, as well as fix all occurrences in the document, project, or solution by clicking on the appropriate link:After Visual Studio has added the changes displayed in the suggestions window, your Lion
class will be correct and will look like the code listed in the following step.
NotImplementedException
exception with the following line of code in each overridden method: throw new NotImplementedException();
:public class Lion : Cat { public override void Eat() { throw new NotImplementedException(); } public override void Hunt() { throw new NotImplementedException(); } public override void Sleep() { throw new NotImplementedException(); } }
This is the default behavior of Visual Studio when overriding methods in the base class. Basically, if you had to instantiate the Lion
class without writing any implementation in the overridden methods, a runtime exception would be generated. The idea of inheriting from our abstract class was to extend it and implement common functionality. This is where we need to implement that functionality, and this is the reason there is no implementation in the abstract class. The abstract class just tells us that the following methods need to be implemented. The derived class does the actual implementation.
Lion
class. First, add the using static
statement for the Console.WriteLine
method to the top of your class file:using static System.Console;
public override void Eat() { WriteLine($"The {LionColor} lion eats."); } public override void Hunt() { WriteLine($"The {LionColor} lion hunts."); } public override void Sleep() { WriteLine($"The {LionColor} lion sleeps."); }
Tiger
that also derives from the abstract class Cat
. Follow step 7 to step 12 to create the Tiger
class and inherit the Cat
abstract class:public class Tiger : Cat { public override void Eat() { throw new NotImplementedException(); } public override void Hunt() { throw new NotImplementedException(); } public override void Sleep() { throw new NotImplementedException(); } }
Tiger
class, as follows:public override void Eat() { WriteLine($"The {TigerColor} tiger eats."); } public override void Hunt() { WriteLine($"The {TigerColor} tiger hunts."); } public override void Sleep() { WriteLine($"The {TigerColor} tiger sleeps."); }
Lion
class, add an enumerator for ColorSpectrum
and a property called LionColor
. It is here that the implementations of the Lion
and Tiger
classes will differ. While they both must implement the common functionality specified in the abstract class, namely Eat()
, Hunt()
, and Sleep()
, only the lion can have a color of either brown or white in its available range of colors:public enum ColorSpectrum { Brown, White } public string LionColor { get; set; }
Lion()
constructor in our Lion
class. This will allow us to specify a color for the lions in the cat sanctuary. The constructor also takes as parameter a variable of the ColorSpectrum
enumerator type:public Lion(ColorSpectrum color) { LionColor = color.ToString(); }
Tiger
class can only have a ColorSpectrum
enumeration that defines tigers as being orange, white, gold, blue (yes, you actually get a blue tiger), or black. Add the ColorSpectrum
enumerator to the Tiger
class, as well as a property called TigerColor
:public enum ColorSpectrum { Orange, White, Gold, Blue, Black } public string TigerColor { get; set; }
Tiger()
constructor for our Tiger
class to set the colors of tigers in the cat sanctuary to the valid colors that tigers are found in. By doing this, we are separating certain functionality specific only to tigers and lions in their respective classes, while all the common functionality is contained in the abstract class Cat
:public Tiger(ColorSpectrum color) { TigerColor = color.ToString(); }
Chapter2.cs
class file. Right-click on References in the console application project:CodeSamples
project. Select Chapter2
and click on the OK button. Then, add the using Chapter2;
statement:Lion
and Tiger
classes. You will see that we set the respective cat's color from the constructor:Lion lion = new Lion(Lion.ColorSpectrum.White); lion.Hunt(); lion.Eat(); lion.Sleep(); Tiger tiger = new Tiger(Tiger.ColorSpectrum.Blue); tiger.Hunt(); tiger.Eat(); tiger.Sleep(); Console.ReadLine();
While the example illustrated earlier is a rather simplistic one, the theory is sound. The abstract class takes collective functionality across all cats and groups so that it can be shared inside each derived class. No implementation exists in the abstract class; it only defines what needs to happen. Think of abstract classes as a type of blueprint for classes that inherit from the abstract class.
While the content of the implementation is up to you, the abstract class requires that you add the abstract methods it defines. From here on, you can create a solid foundation for similar classes in your applications that are supposed to share functionality. This is the goal of inheritance. Let's recap the features of an abstract class:
new
keyword.sealed
. The sealed
modifiers prevents inheritance, while abstract requires inheritance.