A Guide to C# Interfaces

A foundational guide to interfaces in C#. Learn what an interface is, how it differs from a class, and why interfaces are a crucial tool for building flexible, loosely coupled, and testable applications.

In object-oriented programming, an interface is a contract. It defines a set of public methods, properties, events, or indexers that a class or struct must implement. The interface itself provides no implementation; it only specifies the "what," not the "how."

Interfaces are a cornerstone of building flexible and maintainable applications in C#. They are the key to achieving abstraction and enabling patterns like dependency injection.

What is an Interface?

Think of an interface as a job description. It lists the responsibilities and capabilities that are required for a certain role, but it doesn't say anything about the person (the class) who will perform that role.

For example, you could define an ILogger interface that defines the contract for logging messages:

public interface ILogger
{
    void LogInfo(string message);
    void LogError(string message, Exception ex);
}

This interface says, "Any class that wants to be considered a logger must provide an implementation for a LogInfo method and a LogError method."

By convention, interface names in C# start with the letter I.

Implementing an Interface

A class can then implement this interface. This means it promises to provide a concrete implementation for all the members defined in the interface.

We could have a FileLogger that writes messages to a file:

public class FileLogger : ILogger
{
    public void LogInfo(string message)
    {
        // Write the message to a file...
    }

    public void LogError(string message, Exception ex)
    {
        // Write the error to a file...
    }
}

And we could have a ConsoleLogger that writes messages to the console:

public class ConsoleLogger : ILogger
{
    public void LogInfo(string message)
    {
        Console.WriteLine(message);
    }

    public void LogError(string message, Exception ex)
    {
        Console.Error.WriteLine(message);
    }
}

Both classes fulfill the ILogger contract, but they do so in completely different ways.

Why Are Interfaces So Important?

The power of interfaces comes from the ability to write code that depends on the interface, not on a specific concrete class. This is known as programming to an interface, and it's a fundamental principle of good software design.

Imagine you have a class that needs to do some logging:

public class DataProcessor
{
    private readonly ILogger _logger;

    // The constructor accepts any object that implements ILogger
    public DataProcessor(ILogger logger)
    {
        _logger = logger;
    }

    public void Process()
    {
        _logger.LogInfo("Starting data processing...");
        // ... do work ...
        _logger.LogInfo("Data processing finished.");
    }
}

The DataProcessor class doesn't know or care whether it's getting a FileLogger or a ConsoleLogger. It only knows that it has an object that fulfills the ILogger contract. This is called loose coupling.

This design provides several major benefits:

  • Flexibility: You can change the logging implementation without having to change the DataProcessor class at all. You could create a new DatabaseLogger and pass it to the DataProcessor, and it would just work.

  • Testability: When you are writing a unit test for DataProcessor, you don't want to actually write to a file or the console. Instead, you can create a "mock" or "fake" logger class that implements ILogger just for the test. This allows you to test the DataProcessor in isolation.

  • Enabling Dependency Injection: The pattern of passing dependencies (like the ILogger) into a class's constructor is called Dependency Injection, and it relies heavily on interfaces.

Interfaces vs. Abstract Classes

Interfaces are similar to abstract classes, but there are key differences:

  • A class can implement multiple interfaces, but it can only inherit from one class.
  • An abstract class can provide some default implementation for its methods, whereas an interface (prior to C# 8.0) could not.

Generally, you should prefer interfaces when you are defining a contract that can be implemented by a variety of unrelated classes. You should use an abstract class when you want to provide some common, shared base functionality for a group of related classes.

Conclusion

Interfaces are a fundamental and powerful tool in the C# language. They are the key to writing code that is loosely coupled, flexible, and easily testable. By defining clear contracts between the different parts of your application, interfaces allow you to build complex systems that are easier to maintain, extend, and adapt over time.