C# Interfaces vs. Abstract Classes: A Developer's Guide
A clear and practical comparison of interfaces and abstract classes in C#. Understand the key differences and learn when to use each one to write better, more flexible object-oriented code.
In object-oriented programming with C#, two of the most important tools for creating flexible and extensible code are interfaces and abstract classes. They both define contracts that other classes can adhere to, but they do so in different ways and are used for different purposes. Understanding the difference is key to writing good C# code.
What is an Interface?
An interface is a pure contract. It defines a set of public methods, properties, events, or indexers that a class must implement. It contains no implementation details itself (with the exception of default interface methods since C# 8.0).
Think of an interface as defining a "can do" relationship. If a class implements the IDisposable
interface, it's saying, "I can be disposed of." If it implements IEnumerable
, it's saying, "I can be enumerated."
Example:
public interface ILogger
{
void Log(string message);
void LogError(string message, Exception ex);
}
// A class that implements the interface
public class FileLogger : ILogger
{
public void Log(string message)
{
// Implementation for logging to a file
}
public void LogError(string message, Exception ex)
{
// Implementation for logging an error to a file
}
}
What is an Abstract Class?
An abstract class is a class that cannot be instantiated on its own and must be inherited by another class. It's a mix between a contract and a regular class. It can contain both abstract members (which have no implementation and must be overridden by the subclass) and concrete members (which have an implementation and can be used by the subclass).
Think of an abstract class as defining an "is a" relationship. It provides a common base for a family of related classes. For example, a Car
class and a Motorcycle
class might both inherit from an abstract Vehicle
class.
Example:
public abstract class Vehicle
{
// Concrete property with implementation
public int NumberOfWheels { get; protected set; }
// Abstract method - must be implemented by subclasses
public abstract void StartEngine();
// Concrete method with implementation
public void Honk()
{
Console.WriteLine("Beep beep!");
}
}
// A class that inherits from the abstract class
public class Car : Vehicle
{
public Car()
{
NumberOfWheels = 4;
}
public override void StartEngine()
{
Console.WriteLine("Car engine started.");
}
}
Key Differences
Feature | Interface | Abstract Class |
---|---|---|
Multiple Inheritance | A class can implement many interfaces. | A class can inherit from only one class. |
Implementation | Cannot contain fields or implemented methods (before C# 8). | Can contain fields and implemented methods. |
Access Modifiers | All members are implicitly public. | Members can have any access modifier (public, protected, etc.). |
Purpose | Defines a capability ("can do"). | Defines a common identity ("is a"). |
Instantiation | Cannot be instantiated. | Cannot be instantiated. |
When to Use an Interface
You should prefer using an interface when:
- You expect that unrelated classes will need to implement the contract. For example, many different classes might need to be loggable (
ILogger
). - You want to define the functionality of a small, self-contained capability (e.g.,
IEquatable
,IComparable
). - You need to take advantage of multiple inheritance of type.
When to Use an Abstract Class
You should consider using an abstract class when:
- You want to share code among several closely related classes. The abstract class can provide a common set of implemented methods and fields.
- You expect that the classes that inherit from your abstract class have a common base type and identity.
- You need to provide components that have a default implementation but can be overridden.
- You need to control the versioning of your base class. If you add a new method to an interface, you break all the classes that implement it. If you add a new method to an abstract class, you can provide a default implementation and not break existing code.
A Simple Rule of Thumb
Start with an interface. If you find yourself needing to share implementation code between the implementing classes, then consider refactoring to an abstract class.
In modern C# development, especially with the rise of dependency injection, interfaces are often the more flexible and preferred choice for defining the contracts between different layers of an application. They provide a powerful way to decouple your components and make your code more testable and maintainable.