Abstract Classes and Interfaces
Abstract classes and interfaces can be used for similar purposes but it's important to understand the differences between the two.
Abstract Classes
An abstract class is a base class that has zero or more members that are abstract, meaning that they are declared but are not defined. Unlike regular classes, abstract classes cannot be instantiated and must be inherited.
You can make a class abstract by simply using the abstract
keyword before the class
keyword. Once you've done so, any of the class members (properties, fields, and methods) can also be declared abstract. An abstract member can't have an implementation. Note in the example below that the method has the abstract
keyword in its declaration and thus is not allowed to have a body (implementation).
One strength of abstract classes is that they allow you to have baseline functionality in your class but they can also require users of your class to implement their own aspects of the code. In the example below, there's an EntityBase
class that contains some default behavior like IsNew
and IsValid
because those methods have working implementations, but the Validate
method is abstract and requires the inheriting class to provide its own implementation. Notice how the IsValid
method even makes use of the Validate
method, even though it's not defined.
Another strength of abstract classes is that you can make non-public members abstract. In other words, you can force the user of your class to implement protected and internal members, in addition to public ones.
A weakness of abstract classes is that you cannot inherit from more than one of them.
Interface
An interface is a contract that has zero or more members that must be implemented by any class that implements the interface. As of C# 8.0, interfaces may contain default implementations, in which case, the implementing class will not be required to implement members for which there are default implementations. Like an abstract class, interfaces cannot be instantiated.
To implement an interface, you simply specify the interface after the class name as shown below. In the example below, there's a Person
class that implements an IEntity
interface. The interface defines some members that the class must implement to be a valid IEntity
.
One strength of interfaces is that you can implement as many of them as you'd like. So you could have a FordFusion
class that implements both ICar
and IGasPoweredVehicle
. This would allow you to pass an instance of your FordFusion
class into any methods that accept ICar
or IGasPoweredVehicle
.
Members of interfaces are always public. This could be considered a weakness, but it's also kind of the defining characteristic of interfaces, so it's hard to call it such. Either way, it's definitely something to be aware of.
A weakness of interfaces is that, before C# 8.0, they couldn't have default implementations. As of C# 8.0, you can have default implementations in interfaces, but said behavior can only interact with members that the interface has declared. And because interface members can only be public, this means that your default implementations can only deal with members that are also public.
Here's the same interface example as above, but this time, we're taking advantage of C# 8.0's default implementations support. Notice how we don't have to declare the IsNew
or IsValid
methods on the Person
class because IEntity
contains default implementations of those members. We could still declare those methods if we wanted to supply our own implementations, but we're no longer required to.
Comparison
The following table is an at-a-glance comparison between the two code constructs.
Feature | Abstract Class | Interface |
---|---|---|
Multiple Inheritance | No. | Yes. |
Default Implementations | Yes. | Yes (in C# 8.0). |
Access Modifiers | Yes. | No, everything is public. |
When to Use
Use an abstract class if:
- You have a base class that needs to utilize private, protected, or internal members.
Use an interface if:
- You want to support multiple inheritance.
- You want default implementations that may or may not make use of other interface members. (Only applies to C# 8.0 or greater)
- You want to apply to a
struct
.