| Feature | Abstract Class | Interface |
|---|---|---|
| Can be instantiated? | No | No |
| Can contain implementation? | Yes | Yes, modern C# supports default implementations |
| Can contain fields? | Yes | No instance fields |
| Can have constructors? | Yes | No instance constructors |
| Multiple inheritance | No | A class can implement multiple interfaces |
| Shared state | Yes | No instance state |
| Best for | Closely related types | Contracts/capabilities |
| Dependency Injection | Possible | Very common |
| Common implementation | Strong support | Limited/default implementations |
| Versioning considerations | Base-class changes can affect derived types | Interface evolution requires care |
10. Choosing Between an Abstract Class and an Interface
Here are some practical guidelines.
Use an abstract class when:
- The types have a strong "is-a" relationship.
- You need to share common implementation.
- You need common state or fields.
- You need constructors.
- You want to provide a common workflow.
- Derived classes are expected to follow a common architecture.
Example:
PaymentProcessor|┌─────┴─────┐CardPayment BankPayment
Use an interface when:
- You want to define a contract.
- Different and potentially unrelated classes need the same capability.
- You want multiple capabilities on a class.
- You are designing for dependency injection.
- You want to reduce coupling.
- You want to make implementations easier to replace or mock.
Example:
ICacheable↑┌─┴───────────────┐Customer Product
11. Keep Interfaces Small
A useful modern design principle is to avoid creating very large interfaces.
Instead of:
public interface IUserService{void Create();void Update();void Delete();void SendEmail();void GenerateReport();void Export();}
consider separating responsibilities:
public interface IUserRepository{Task<User?> GetAsync(int id);}
public interface IUserNotificationService{Task SendAsync(User user);}
public interface IUserReportService{Task GenerateAsync();}
This follows the Interface Segregation Principle and generally produces easier-to-maintain code.
12. Don't Automatically Choose an Interface
Interfaces are extremely common in modern .NET applications, particularly because of dependency injection. However, adding an interface to every class isn't automatically good design.
For example, creating:
IUserServiceUserService
only because "every service needs an interface" can introduce unnecessary abstraction.
The better question is:
What design problem does this abstraction solve?
If the abstraction provides value through polymorphism, dependency inversion, testing, multiple implementations, or architectural boundaries, an interface can be useful.
Otherwise, a concrete class may be perfectly appropriate.
13. A Simple Rule of Thumb
A practical way to remember the difference is:
Abstract class = shared foundation
Interface = shared contract or capability
For example:
Abstract ClassAnimal|├── Dog└── Cat
They share common characteristics and behavior.
Whereas:
InterfaceIFlyable↑├── Bird├── Airplane└── Drone
These types don't need to belong to the same class hierarchy, but they share a capability.
Conclusion
Abstract classes and interfaces are both powerful mechanisms for abstraction and polymorphism in C#, but they should be used for different design purposes.
Use an abstract class when you need a common base, shared state, shared implementation, or a common workflow among closely related types.
Use an interface when you need to define a contract or capability that can be implemented by multiple, potentially unrelated types.
With modern C# and .NET, interfaces have become even more capable, while dependency injection, SOLID principles, composition, and loose coupling have made interfaces particularly important in application architecture.
Comments
Post a Comment