Interface Basics
Vaibhav • September 10, 2025
In the previous article, we explored sealed classes - a way to finalize behavior and prevent further inheritance. Now we shift focus to a more flexible and decoupled design tool: interfaces. Interfaces are foundational to C# programming and object-oriented design. They allow you to define contracts without implementation, enabling multiple inheritance, polymorphism, and clean separation of concerns. In this article, we’ll unpack what interfaces are, how they differ from classes, how to implement them, and why they’re essential in real-world applications.
What is an interface?
An interface is a reference type that defines a set of members - methods, properties, events, or indexers -
without providing any implementation. It’s like a blueprint that classes agree to follow. You declare an
interface using the interface
keyword.
interface IPrintable
{
void Print();
}
This interface defines a single method Print()
. Any class that implements IPrintable
must provide a concrete implementation of that method. Interfaces
don’t contain fields, constructors, or method bodies - they only describe what a class should do, not how.
By convention, interface names in C# start with a capital “I” - like IComparable
, IDisposable
, or IEnumerable
.
Implementing an interface
To implement an interface, a class uses a colon followed by the interface name. The class must then provide concrete implementations for all members defined in the interface.
class Document : IPrintable
{
public void Print()
{
Console.WriteLine("Printing document...");
}
}
The Document
class implements IPrintable
by
defining the Print()
method. If it fails to do so, the compiler will raise an
error. This ensures that any object claiming to be IPrintable
actually supports
the required behavior.
Interfaces vs abstract classes
Both interfaces and abstract classes define contracts, but they differ in key ways. Interfaces cannot contain implementation (until C# 8.0, which introduced default interface methods - we’ll cover that later). Abstract classes can include both abstract and concrete members. Interfaces support multiple inheritance; classes do not.
abstract class Shape
{
public abstract double Area();
}
interface IResizable
{
void Resize(double factor);
}
class Circle : Shape, IResizable
{
public double Radius;
public Circle(double radius)
{
Radius = radius;
}
public override double Area()
{
return Math.PI * Radius * Radius;
}
public void Resize(double factor)
{
Radius *= factor;
}
}
Here, Circle
inherits from an abstract class Shape
and implements the interface IResizable
.
This combination allows it to inherit behavior and also commit to a contract. Interfaces are especially useful
when you want to support multiple behaviors across unrelated types.
Polymorphism with interfaces
Interfaces enable polymorphism - the ability to treat different objects through a common interface. This is powerful for writing flexible and reusable code.
interface IPlayable
{
void Play();
}
class Song : IPlayable
{
public void Play()
{
Console.WriteLine("Playing song...");
}
}
class Video : IPlayable
{
public void Play()
{
Console.WriteLine("Playing video...");
}
}
void PlayMedia(IPlayable media)
{
media.Play();
}
The PlayMedia()
method accepts any object that implements IPlayable
. Whether it’s a Song
or a Video
, the method can invoke Play()
without
knowing the exact type. This is the essence of interface-based polymorphism.
Interface inheritance
Interfaces can inherit from other interfaces. This allows you to build complex contracts from simpler ones. A class implementing the derived interface must implement all members from the entire hierarchy.
interface IReadable
{
void Read();
}
interface IWritable
{
void Write();
}
interface IFile : IReadable, IWritable
{
void Open();
}
class TextFile : IFile
{
public void Read()
{
Console.WriteLine("Reading text file...");
}
public void Write()
{
Console.WriteLine("Writing to text file...");
}
public void Open()
{
Console.WriteLine("Opening text file...");
}
}
The TextFile
class implements IFile
, which
inherits from IReadable
and IWritable
. This
pattern is common in large systems where interfaces are layered to reflect capabilities.
Explicit interface implementation
Sometimes, a class implements multiple interfaces that define methods with the same signature. To avoid ambiguity, you can use explicit interface implementation. This also hides the method from public access unless accessed via the interface.
interface IPrinter
{
void Print();
}
interface IScanner
{
void Print();
}
class MultiFunctionDevice : IPrinter, IScanner
{
void IPrinter.Print()
{
Console.WriteLine("Printing from printer...");
}
void IScanner.Print()
{
Console.WriteLine("Printing from scanner...");
}
}
Here, MultiFunctionDevice
implements both IPrinter
and IScanner
. Each Print()
method is implemented explicitly. You must cast the object to the
appropriate interface to call the correct method.
Explicit interface methods are not accessible via the class instance directly. You must cast to the interface to invoke them.
Interfaces and collections
Many built-in collections in C# use interfaces to define behavior. For example, IEnumerable<T>
defines the ability to be iterated over, and IList<T>
adds indexing and modification capabilities.
List names = new List { "Alice", "Bob", "Charlie" };
IEnumerable readable = names;
foreach (string name in readable)
{
Console.WriteLine(name);
}
Here, the List<string>
is treated as an IEnumerable<string>
. This allows iteration without exposing modification
methods. Interfaces help define what operations are allowed, improving safety and clarity.
Interfaces and dependency injection
Interfaces are central to dependency injection - a design pattern that promotes loose coupling. Instead of hardcoding dependencies, you depend on interfaces and inject concrete implementations at runtime.
interface ILogger
{
void Log(string message);
}
class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"Log: {message}");
}
}
class Processor
{
private readonly ILogger _logger;
public Processor(ILogger logger)
{
_logger = logger;
}
public void Run()
{
_logger.Log("Processing started...");
}
}
The Processor
class depends on ILogger
, not
ConsoleLogger
. This allows you to swap in different loggers (e.g., FileLogger
, DatabaseLogger
) without changing the
Processor
code. This pattern is widely used in enterprise applications.
Always depend on interfaces, not concrete classes. This makes your code more flexible, testable, and maintainable.
Interfaces and testing
Interfaces make unit testing easier. You can create mock implementations of interfaces to simulate behavior without relying on real dependencies.
class MockLogger : ILogger
{
public List Messages = new List();
public void Log(string message)
{
Messages.Add(message);
}
}
This MockLogger
records messages instead of printing them. You can use it in
tests to verify that logging occurred as expected. This technique is common in test-driven development.
Summary
Interfaces are a cornerstone of clean, flexible, and scalable C# design. They define contracts without implementation, support multiple inheritance, enable polymorphism, and promote loose coupling. You’ve learned how to declare and implement interfaces, how they differ from abstract classes, how to use them in collections, dependency injection, and testing. Interfaces are everywhere in real-world C# - from LINQ to ASP.NET to Unity - and mastering them will elevate your design skills significantly.
In the next article, we’ll explore Polymorphism Concepts - how objects behave differently based on their runtime type, and how interfaces and inheritance make this possible.