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.