Skip to content

C# Interfaces — Defning Contracts, Implementation, and Default Interface Methods

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about C# Interfaces. We cover key concepts, practical examples, and best practices to help you master this topic.

C# interfaces define contracts that classes and structs can implement, enabling polymorphic behavior across unrelated types and supporting default method implementations since C# 8.

What You'll Learn

You will master interfaces in C#: defining interface contracts, implementing interfaces in classes and structs, using explicit interface implementation for disambiguation, leveraging default interface methods introduced in C# 8, and understanding how interfaces enable .NET features like dependency injection and mocking.

Why It Matters

Interfaces are fundamental to modern C# development. They enable loose coupling, testability through mocking, dependency injection, and the ability to write code that works with any type that satisfies a contract. Frameworks like ASP.NET Core are built entirely around interfaces. Without interfaces, you cannot effectively unit test or swap implementations.

Real-World Use

ASP.NET Core uses IServiceCollection, ILogger<T>, IHostedService for extensibility. Entity Framework Core uses IDbContextFactory<T>. LINQ integrates with IEnumerable<T>. Repository patterns use interfaces for testability. Logging frameworks abstract behind ILogger. HttpClient uses HttpMessageHandler.

Learning Path

graph LR
    A["13: Polymorphism"] --> B["14: Interfaces"]
    B --> C["15: Records"]
    C --> D["16: Structs"]
    D --> E["17: Strings"]
    style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style E fill:#4a90d9,stroke:#2c5f8a,color:#fff

Defining and Implementing Interfaces

public interface ILogger
{
    void Log(string message);
    void LogError(string message, Exception? ex = null);
}

public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"[INFO] {message}");
    }

    public void LogError(string message, Exception? ex = null)
    {
        Console.WriteLine($"[ERROR] {message}");
        if (ex != null) Console.WriteLine($"  Exception: {ex.Message}");
    }
}

public class FileLogger : ILogger
{
    private readonly string _path;

    public FileLogger(string path) => _path = path;

    public void Log(string message)
    {
        File.AppendAllText(_path, $"[INFO] {DateTime.UtcNow}: {message}\n");
    }

    public void LogError(string message, Exception? ex = null)
    {
        File.AppendAllText(_path, $"[ERROR] {DateTime.UtcNow}: {message}\n");
        if (ex != null) File.AppendAllText(_path, $"  Exception: {ex.Message}\n");
    }
}

// Polymorphic usage
ILogger logger = new ConsoleLogger();
logger.Log("Application started");
logger = new FileLogger("app.log");
logger.Log("Logging to file");

Interface Members

Interfaces can declare:

public interface IRepository<T>
{
    // Method
    T GetById(int id);

    // Property
    bool IsReadOnly { get; }

    // Indexer
    T this[int id] { get; }

    // Event
    event EventHandler<T>? ItemAdded;

    // Default method (C# 8+)
    void LogAccess(string operation)
    {
        Console.WriteLine($"Access: {operation} on {typeof(T).Name}");
    }
}

Explicit Interface Implementation

Used when implementing the same method signature from multiple interfaces:

public interface IWriter
{
    void Write(string text);
}

public interface IFormatter
{
    void Write(string text);
}

public class Document : IWriter, IFormatter
{
    private string _content = "";

    // Explicit implementation for IWriter
    void IWriter.Write(string text)
    {
        _content += text;
        Console.WriteLine($"Writer: {text}");
    }

    // Explicit implementation for IFormatter
    void IFormatter.Write(string text)
    {
        _content += $"<formatted>{text}</formatted>";
        Console.WriteLine($"Formatter: <formatted>{text}</formatted>");
    }

    // Public method that delegates
    public void Append(string text) => ((IWriter)this).Write(text);

    public string Content => _content;
}

var doc = new Document();
// doc.Write("test");  // Error: ambiguous
((IWriter)doc).Write("Hello");   // Writer: Hello
((IFormatter)doc).Write("World"); // Formatter: <formatted>World</formatted>
doc.Append("Direct");  // Writer: Direct
Console.WriteLine($"Content: {doc.Content}");

Default Interface Methods (C# 8)

Interfaces can now provide default implementations:

public interface IEntity
{
    int Id { get; }
    DateTime CreatedAt { get; }

    // Default implementation
    bool IsNew => Id == 0;

    string GetDescription() => $"Entity #{Id} (created: {CreatedAt:yyyy-MM-dd})";
}

public class User : IEntity
{
    public int Id { get; init; }
    public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
    public string Name { get; set; } = "";

    // Optional: override default
    public string GetDescription() => $"User: {Name} (ID: {Id})";
}

public class Product : IEntity
{
    public int Id { get; init; }
    public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
    public string ProductName { get; set; } = "";
    // Uses default GetDescription()
}

var user = new User { Id = 1, Name = "Alice" };
var product = new Product { Id = 100, ProductName = "Widget" };

Console.WriteLine(user.GetDescription());    // User: Alice (ID: 1)
Console.WriteLine(product.GetDescription()); // Entity #100 (created: 2026-06-28)
Console.WriteLine($"User is new: {user.IsNew}");  // False

Interface Inheritance

public interface IReadOnlyRepository<T>
{
    T? GetById(int id);
    IEnumerable<T> GetAll();
}

public interface IRepository<T> : IReadOnlyRepository<T>
{
    void Add(T entity);
    void Update(T entity);
    void Delete(int id);
}

public class Repository<T> : IRepository<T>
{
    private readonly Dictionary<int, T> _data = new();
    private int _nextId = 1;

    public T? GetById(int id) =>
        _data.TryGetValue(id, out var item) ? item : default;

    public IEnumerable<T> GetAll() => _data.Values;

    public void Add(T entity)
    {
        // Add implementation
    }

    public void Update(T entity) { }

    public void Delete(int id) => _data.Remove(id);
}

Multiple Interface Inheritance

A class can implement multiple interfaces:

public interface IPrintable
{
    void Print();
}

public interface ISerializable
{
    string Serialize();
}

public interface IExportable : IPrintable, ISerializable
{
    void Export(string filePath);
}

public class Report : IExportable
{
    public string Title { get; set; }
    public string Content { get; set; }

    public void Print()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.WriteLine(Content);
    }

    public string Serialize()
    {
        return $"{{\"title\":\"{Title}\",\"content\":\"{Content}\"}}";
    }

    public void Export(string filePath)
    {
        File.WriteAllText(filePath, Serialize());
        Console.WriteLine($"Exported to {filePath}");
    }
}

Interfaces vs Abstract Classes

// Interface: contract only
public interface IMovable
{
    void Move(int x, int y);
    double Speed { get; }
}

// Abstract class: shared implementation
public abstract class Vehicle
{
    public string Name { get; set; }
    public abstract void Start();
    public virtual void Stop() => Console.WriteLine("Vehicle stopped");
}

Choose interfaces when:

  • Defining a contract across unrelated types
  • Supporting multiple inheritance
  • You only need behavior, no shared state

Choose abstract classes when:

  • Providing shared implementation
  • Having shared fields or constructors
  • The types are related

Common Mistakes

Mistake 1: Defining Interfaces That Are Too Large

Interfaces should follow the Interface Segregation Principle. Large interfaces force implementing classes to provide methods they do not need. Split into smaller, focused interfaces.

Mistake 2: Using Interfaces When a Delegate Suffices

For single-method contracts, consider Action, Func, or custom delegates instead of defining an interface.

Mistake 3: Not Using Explicit Implementation When Required

When a class implements two interfaces with the same member, use explicit implementation to disambiguate. Otherwise, calling code cannot differentiate.

Mistake 4: Adding Members to Published Interfaces Without Default Implementations

Adding a method to a public interface breaks all existing implementations. Default interface methods (C# 8+) solve this but should be used carefully.

Mistake 5: Confusing Interface Inheritance with Class Inheritance

Interface inheritance is about extending contracts, not inheriting implementation. A class that implements a derived interface must implement all members from all interfaces in the hierarchy.

Mistake 6: Forgetting That Interfaces Cannot Have State

Interfaces cannot contain fields, auto-properties with backing fields, or constructors. They can only define method signatures, properties (without body), events, and indexers.

Practice Questions

  1. What is the difference between an interface and an abstract class?
  2. When would you use explicit interface implementation?
  3. How do default interface methods affect existing implementations?
  4. Why would you define an interface instead of using a concrete class?
  5. Write an IDataSource<T> interface with methods for CRUD operations and a default method for logging.

Challenge

Design an IPaymentGateway interface with methods for ProcessPayment, Refund, and GetTransactionStatus. Implement it with StripeGateway and PayPalGateway classes. Add a default LogTransaction method to the interface.

FAQ

Can interfaces have properties?

Yes. Interfaces can define properties, events, indexers, and methods. Properties in interfaces do not have implementation (unless using default interface methods in C# 8+).

What happens if I implement an interface implicitly and explicitly?

Explicit implementations take precedence over implicit ones. When called through the interface type, the explicit implementation is used. When called through the class type, the implicit implementation is used.

Can structs implement interfaces?

Yes. Structs can implement interfaces, which enables polymorphism for value types. However, boxing occurs when a value type is treated as an interface reference.

Are default interface methods the same as virtual methods?

Similar but different. Default interface methods are not inherited like virtual methods. If a class implements the interface without overriding, the default is used. The class can still choose to provide its own implementation.

Can I create an instance of an interface?

No. Interfaces cannot be instantiated directly. You can only create instances of classes that implement the interface and assign them to interface-typed variables.

Mini Project

Create a plugin-based document processing system using interfaces:

public interface IDocumentProcessor
{
    string Name { get; }
    bool CanProcess(string fileName);
    string Process(string content);
}

public class MarkdownProcessor : IDocumentProcessor
{
    public string Name => "Markdown Processor";

    public bool CanProcess(string fileName) =>
        fileName.EndsWith(".md", StringComparison.OrdinalIgnoreCase);

    public string Process(string content)
    {
        // Simple Markdown-to-HTML conversion
        var html = content
            .Replace("**", "<strong>").Replace("**", "</strong>")
            .Replace("*", "<em>").Replace("*", "</em>");
        return $"<h1>Markdown Output</h1>\n{html}";
    }
}

public class CsvProcessor : IDocumentProcessor
{
    public string Name => "CSV Processor";

    public bool CanProcess(string fileName) =>
        fileName.EndsWith(".csv", StringComparison.OrdinalIgnoreCase);

    public string Process(string content)
    {
        var lines = content.Split('\n');
        var headers = lines[0].Split(',');
        var output = "<table>\n  <tr>";
        foreach (var h in headers) output += $"<th>{h.Trim()}</th>";
        output += "</tr>\n";
        for (int i = 1; i < lines.Length; i++)
        {
            if (string.IsNullOrWhiteSpace(lines[i])) continue;
            output += "  <tr>";
            foreach (var cell in lines[i].Split(','))
                output += $"<td>{cell.Trim()}</td>";
            output += "</tr>\n";
        }
        output += "</table>";
        return output;
    }
}

public class DocumentPipeline
{
    private readonly List<IDocumentProcessor> _processors = new();

    public void RegisterProcessor(IDocumentProcessor processor)
    {
        _processors.Add(processor);
    }

    public string ProcessDocument(string fileName, string content)
    {
        var processor = _processors.FirstOrDefault(p => p.CanProcess(fileName));
        if (processor == null)
            throw new NotSupportedException($"No processor found for {fileName}");

        Console.WriteLine($"Processing with: {processor.Name}");
        return processor.Process(content);
    }
}

var pipeline = new DocumentPipeline();
pipeline.RegisterProcessor(new MarkdownProcessor());
pipeline.RegisterProcessor(new CsvProcessor());

var mdResult = pipeline.ProcessDocument("readme.md", "# Hello\nThis is **bold** text");
Console.WriteLine(mdResult);

Console.WriteLine();

var csvResult = pipeline.ProcessDocument("data.csv", "Name,Age\nAlice,30\nBob,25");
Console.WriteLine(csvResult);

Expected output:

Processing with: Markdown Processor
<h1>Markdown Output</h1>
<h1>Hello</h1>
This is <strong>bold</strong> text

Processing with: CSV Processor
<table>
  <tr><th>Name</th><th>Age</th></tr>
  <tr><td>Alice</td><td>30</td></tr>
  <tr><td>Bob</td><td>25</td></tr>
</table>

What's Next

You have mastered interfaces in C#. The next lesson covers records: record class, record struct, positional syntax, and with expressions for immutable Data Modeling.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro