Summary: in this tutorial, you’ll learn when to use an abstract class and when to use an interface and the differences between an abstract class and an interface.
Choosing between an abstract class and an interface
In C#, an abstract class is similar to an interface. However, the abstract class and interface serve different purposes. Therefore, it’s important to know when to use an abstract class and when to use an interface.
Generally, when you want to model the is-a relationship and share a common implementation with all the subclasses, you use an abstract class.
But when you want to create a contract that other classes must adhere to, you use an interface.
The following example demonstrates how to use the abstract class Shape that has the Display() method shared by all of its subclasses:
abstract class Shape
{
public abstract double GetArea();
public abstract double GetPerimeter();
public void Display()
{
Console.WriteLine($"Area: {GetArea():F2}, Perimeter: {GetPerimeter():F2}");
}
}
class Rectangle : Shape
{
readonly double length;
readonly double width;
public Rectangle(double length, double width)
{
this.length = length;
this.width = width;
}
public override double GetArea()
{
return length * width;
}
public override double GetPerimeter()
{
return 2 * (length + width);
}
}
class Circle : Shape
{
readonly double radius;
public Circle(double radius)
{
this.radius = radius;
}
public override double GetArea()
{
return Math.PI * radius * radius;
}
public override double GetPerimeter()
{
return 2 * Math.PI * radius;
}
}
class Program
{
static void Main(string[] args)
{
var shapes = new List<Shape>()
{
new Rectangle(5, 10),
new Circle(3)
};
foreach (var shape in shapes)
{
shape.Display();
}
}
}
Code language: C# (cs)
Output:
Area: 50.00, Perimeter: 30.00
Area: 28.27, Perimeter: 18.85
Code language: CSS (css)
In this example, the Shape
class is an abstract class that defines two abstract methods GetArea
and GetPerimeter
. Any class that inherits from the Shape class needs to implement these methods.
The Shape
class also has a concrete method Display
that displays the area and perimeter of a shape. All the subclasses of the Shape
class will share the same Display
method.
The Rectangle and Circle classes extend the Shape class. Both of these classes share the same implementation of Display
method, but they each provide their own implementation of GetArea
and GetPerimeter
methods.
The following example demonstrates how to use an interface as a contract:
interface ILogger
{
void Log(string message);
}
class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
class FileLogger : ILogger
{
private readonly string filePath;
public FileLogger(string filePath)
{
this.filePath = filePath;
}
public void Log(string message)
{
using StreamWriter writer = new StreamWriter(filePath, true);
writer.WriteLine(message);
}
}
class Program
{
static void Main(string[] args)
{
ILogger logger;
if (args.Length > 0 && args[0] == "file")
{
logger = new FileLogger("log.txt");
}
else
{
logger = new ConsoleLogger();
}
logger.Log("Starting application...");
// ...
logger.Log("Application stopped.");
}
}
Code language: C# (cs)
In this example, we define the ILogger
interface that has a single method Log
. The Log
method takes a string that represents the message to be logged.
The ConsoleLogger
and FileLogger
classes implement the ILogger
interface and provide their own implementations of the Log
method.
In the Main
method, we create an instance of ConsoleLogger
or FileLogger
based on the command line argument.
If the argument is "file"
, we create a FileLogger
object. Otherwise, we create a ConsoleLogger
object.
Regardless of whether FileLogger
or ConsoleLogger
instance was created, we call the Log
method to log some messages
Since both FileLogger
and ConsoleLogger
classes implement the same interface, we can easily switch between them at runtime.
Abstract class vs. Interface
The following table illustrates the differences between abstract classes and interfaces:
Abstract Classes | Interfaces |
---|---|
Shared implementation | Define a contract |
Can only inherit from a single base class | Can implement any number of interfaces |
Unconstrainted implementation code | Limited implementation code |
Can have automatic properties | No automatic properties |
Summary
- Use an abstract class to model an is-a relationship and share a common implementation with all subclasses.
- Use an interface to represent a contract to which other classes must adhere.