Abstract Classes
Explore the concept of abstract classes in C# to understand how they serve as blueprints in object-oriented programming. This lesson covers creating abstract classes, defining abstract members, and implementing constructors and methods in derived classes. Gain insight into how abstract classes enable code reuse and enforce design contracts by requiring derived classes to provide specific implementations. By the end, you'll comprehend why abstract classes cannot be instantiated directly and how to leverage them for clean, maintainable code.
We'll cover the following...
C# includes abstract classes alongside concrete classes. An abstract class is a class that cannot be instantiated. It can hold shared functionality for its derived classes. This concept is a core pillar of abstraction in C#. By using abstract classes, we can hide the complex implementation details of a base concept and only expose the essential features that derived classes must have.
Abstract classes are useful when we need to define shared behavior without creating instances of the base concept. They serve as a blueprint, allowing us to share common logic while enforcing a design contract that requires derived classes to handle specific implementation details.
Creating an abstract class
Consider the Vehicle, Car, and Motorcycle classes. The Vehicle class holds members common to all vehicles. A vehicle is an abstract concept, while cars and motorcycles are concrete entities.
We mark the Vehicle class as abstract to prevent it from being instantiated directly.
Line 1: We declare a file-scoped namespace to organize the code.
Line 4: We use the
abstractkeyword to define the class. This prevents the class from being instantiated directly.Line 6: We make
Modela nullable string (string?) because it might not be set immediately upon instantiation.Lines 7–8: We define other properties that will be inherited by all derived classes.
Next, we implement a derived class that inherits from this abstract base.
Line 3: We inherit from
Vehiclein theMotorcycleclass.Lines 5–8: The constructor sets
NumberOfWheelsto 2, a property inherited from the abstract base class.
We can define another derived class that extends the same abstract concept.
Line 3: We inherit from
Vehiclein theCarclass as well.Line 5: We add a property specific to cars that does not exist on the base
Vehicleclass.Lines 8: We set the inherited
NumberOfWheelsproperty to 4.
With our classes defined, let us see how we can and cannot instantiate them.
Lines 4 and 7: We successfully create instances of
CarandMotorcycle.Line 11: We create a variable of type
Vehicle, but we assign it aCarinstance, which is allowed becauseCarinherits fromVehicle.Lines 13–15: We access the
Modelproperty, which is defined in the abstractVehicleclass but available on the instances.Line 18: We would trigger a compiler error if we attempted
new Vehicle()becauseVehicleis abstract.
Constructors in abstract classes
Abstract classes can have custom constructors even though they cannot be instantiated directly. These custom constructors can set initial values for properties or perform other common tasks. In turn, these constructors can be called from derived-class constructors to avoid code repetition. Base-class constructors are called using the base keyword.
Lines 9–14: We define a constructor in the abstract
Vehicleclass to initialize its properties.
Next, we must update our derived class to ensure it calls this new constructor correctly.
Line 6: We define a constructor for
Carthat acceptsmodelandprice.Line 7: We pass the specific arguments (
model,price) immediately to thebaseconstructor. We hardcode the value4in thebasecall because all cars have four wheels in this context.
Finally, we can verify that the constructors work as expected by creating an instance in our main program.
Line 4: We create a new
Carinstance, passing “Lexus” and50000to theCarconstructor. TheCarconstructor internally calls thebaseVehicleconstructor, passing these values along with the hardcoded wheel count of 4.Lines 6–8: We print the values to verify that the properties were set correctly by the base constructor.
Abstract members
Abstract classes can have methods, properties, and other members just like concrete classes. Sometimes we must define a method signature without an implementation because there is no meaningful default behavior for derived classes. In these cases, we can mark a member as abstract and skip the implementation block entirely:
public abstract void Accelerate();
For our Vehicle class, we create an Accelerate() method without a body. Vehicles accelerate differently depending on the exact type of vehicle. It may be infeasible to provide a default implementation for this method. Therefore, we mark this method as abstract and delegate implementation responsibility to derived classes. A derived class must implement this method for the code to compile.
Line 10: We mark the
Acceleratemethod asabstractand end it with a semicolon because it has no body in this class.
Now, we must implement this abstract method in our derived classes, starting with the motorcycle.
Line 11: We use the
overridekeyword to provide the concrete implementation forAccelerate.Line 13: We define the implementation logic specific to the
Motorcycle.
The car class must also implement the abstract method, but with logic specific to cars.
Lines 13–16: We override
Acceleratein theCarclass to provide its own unique behavior.
Finally, we can run the program to see how each object handles the method call differently.
Line 3–4: We instantiate the concrete classes.
Line 6: We call
Car.Accelerate(), which prints “Car accelerating.”Line 7: We call
Motorcycle.Accelerate(), which prints “Motorcycle accelerating.”
We must use the override keyword when implementing an abstract method. Abstract classes can also define abstract events, properties, and indexers.
Note: If a derived class does not provide an implementation for inherited abstract members, it must also be declared abstract.