C# Interfaces

Interfaces are a fundamental concept in object-oriented programming, and C# provides powerful support for them. An interface defines a contract that classes can implement. It specifies a set of method signatures, properties, events, and indexers that a class must provide. Interfaces are a way to achieve abstraction and multiple inheritance of type in C#.

What is an Interface?

An interface is declared using the interface keyword. It's a reference type similar to a class, but it cannot contain fields or concrete method implementations (prior to C# 8.0, with default implementations now possible).

Key Characteristics of Interfaces:

Declaring an Interface

You declare an interface using the interface keyword, followed by the interface name. Members within the interface are declared without access modifiers and without method bodies.

Example: Declaring an interface

public interface ILogger
{
    void LogMessage(string message);
    bool IsLoggingEnabled { get; }
}

Implementing an Interface

A class implements an interface by listing the interface name after the class name, separated by a colon. The implementing class must provide concrete implementations for all members declared in the interface. If a class implements an interface, it implicitly inherits from System.Object.

Example: Implementing an interface

public class ConsoleLogger : ILogger
{
    public void LogMessage(string message)
    {
        Console.WriteLine($"LOG: {message}");
    }

    public bool IsLoggingEnabled
    {
        get { return true; }
    }
}

public class FileLogger : ILogger
{
    public void LogMessage(string message)
    {
        // Code to write to a file
        Console.WriteLine($"Writing to file: {message}");
    }

    public bool IsLoggingEnabled
    {
        get { return false; }
    }
}

Using Interfaces

Interfaces are often used to enable polymorphism and to decouple components. You can declare variables of an interface type and assign instances of any class that implements that interface to them.

Example: Using interface variables

public class App
{
    public static void Main(string[] args)
    {
        ILogger consoleLogger = new ConsoleLogger();
        ILogger fileLogger = new FileLogger();

        ProcessData(consoleLogger);
        ProcessData(fileLogger);
    }

    public static void ProcessData(ILogger logger)
    {
        logger.LogMessage("Processing data...");
        /* ... actual data processing ... */
        logger.LogMessage("Data processing complete.");
    }
}

Explicit Interface Implementation

Sometimes, a class may implement multiple interfaces that have members with the same name and signature. To avoid ambiguity or to provide specific implementations for a particular interface, you can use explicit interface implementation. This means the member is not directly accessible on an instance of the class but only when the instance is cast to the interface type.

Example: Explicit Interface Implementation

public interface IControl
{
    void Paint();
}

public interface IScrollable
{
    void Paint();
}

public class UIElement : IControl, IScrollable
{
    // Explicit implementation for IControl
    void IControl.Paint()
    {
        Console.WriteLine("Painting as a control.");
    }

    // Explicit implementation for IScrollable
    void IScrollable.Paint()
    {
        Console.WriteLine("Painting as scrollable content.");
    }

    // Public implementation (optional, if you want a default behavior)
    public void Paint()
    {
        Console.WriteLine("Default painting.");
    }
}

Default Interface Implementations (C# 8.0 and later)

With C# 8.0, interfaces can now provide default implementations for members. This allows you to add new members to an interface without breaking existing code that implements it. Implementing classes can either use the default implementation or provide their own override.

Example: Default Interface Implementation

public interface IDemoInterface
{
    void Method1();

    void Method2()
    {
        Console.WriteLine("This is a default implementation of Method2.");
    }
}

public class MyClass : IDemoInterface
{
    public void Method1()
    {
        Console.WriteLine("Implementing Method1.");
    }

    // MyClass uses the default implementation of Method2
}

public class AnotherClass : IDemoInterface
{
    public void Method1()
    {
        Console.WriteLine("Implementing Method1 in AnotherClass.");
    }

    public void Method2()
    {
        Console.WriteLine("Overriding default implementation of Method2.");
    }
}

Note on Default Implementations

While default implementations offer flexibility, they should be used judiciously. Overuse can sometimes lead to less clear code, as the responsibility for implementation might be spread between the interface and the implementing classes.

Why Use Interfaces?

Tip for Clean Code

When designing your classes and components, consider where interfaces can help to define clear boundaries and responsibilities. The common naming convention is to prefix interface names with 'I' (e.g., ILogger, IDisposable).