Generic Interfaces

Vaibhav • September 11, 2025

In the last few articles, we’ve explored how generics empower us to write reusable, type-safe classes and methods. Now we turn our attention to a powerful abstraction tool that combines the flexibility of generics with the structure of object-oriented design: generic interfaces. These allow us to define contracts that work across types, enabling polymorphism, extensibility, and clean separation of concerns.

What Is a Generic Interface?

An interface defines a set of members that a class must implement. A generic interface does the same-but with a type parameter. This allows the interface to describe behavior that depends on a type, without committing to a specific one.

public interface IRepository<T>
{
    void Add(T item);
    T Get(int id);
}

Here, IRepository<T> defines a contract for storing and retrieving items of type T. Any class that implements this interface must provide logic for Add and Get-but the type of item is flexible.

Implementing a Generic Interface

To implement a generic interface, you can either keep the type parameter generic or specify a concrete type. Let’s look at both approaches.

First, a generic implementation:

public class MemoryRepository<T> : IRepository<T>
{
    private Dictionary<int, T> storage = new Dictionary<int, T>();
    private int nextId = 0;

    public void Add(T item)
    {
        storage[nextId++] = item;
    }

    public T Get(int id)
    {
        return storage[id];
    }
}

This class works for any type. You can create a repository for strings, integers, or custom objects:

var repo = new MemoryRepository<string>();
repo.Add("Hello");
Console.WriteLine(repo.Get(0)); // Output: Hello

Alternatively, you can implement the interface with a specific type:

public class UserRepository : IRepository<User>
{
    private List<User> users = new List<User>();

    public void Add(User user)
    {
        users.Add(user);
    }

    public User Get(int id)
    {
        return users.FirstOrDefault(u => u.Id == id);
    }
}

This version is tailored to User objects and can include domain-specific logic.

You can implement a generic interface multiple times with different type arguments in the same class using explicit interface implementation.

Generic Interfaces and Polymorphism

One of the key benefits of interfaces is polymorphism-the ability to treat different objects through a common interface. Generic interfaces preserve this benefit while adding type safety.

public void SaveAll<T>(IRepository<T> repo, List<T> items)
{
    foreach (var item in items)
    {
        repo.Add(item);
    }
}

This method works with any repository and any item type. You can pass a MemoryRepository<Product> or a UserRepository and it will save all items.

Generic Interfaces in the .NET Framework

The .NET Framework uses generic interfaces extensively. Some common examples include:

public interface IEnumerable<T>
{
    IEnumerator<T> GetEnumerator();
}

public interface IComparer<T>
{
    int Compare(T x, T y);
}

public interface IEqualityComparer<T>
{
    bool Equals(T x, T y);
    int GetHashCode(T obj);
}

These interfaces power LINQ, sorting, hashing, and iteration. You’ve already used them when working with collections like List<T> and Dictionary<K,V>.

Covariance and Contravariance in Interfaces

Generic interfaces support variance, which allows flexibility in type assignments. Covariance lets you use a more derived type, while contravariance lets you use a more base type.

Covariance is supported in output positions (like return types):

public interface IProducer<out T>
{
    T Produce();
}

Contravariance is supported in input positions (like method parameters):

public interface IConsumer<in T>
{
    void Consume(T item);
}

These keywords (out and in) allow safe type substitution and are especially useful in event handling and delegate scenarios.

Variance only works with interfaces and delegates. Classes and structs do not support variance.

Designing Your Own Generic Interfaces

When designing a generic interface, think about what behavior you want to abstract and whether it depends on a type. If it does, make the interface generic.

public interface IValidator<T>
{
    bool IsValid(T item);
}

This interface can be implemented for different types:

public class EmailValidator : IValidator<string>
{
    public bool IsValid(string email)
    {
        return email.Contains("@");
    }
}

You can now write code that validates any type using a consistent pattern.

Generic Interfaces and Dependency Injection

Generic interfaces are widely used in dependency injection (DI) frameworks. You can register services like IRepository<T> and resolve them based on the type.

// Register
services.AddScoped(typeof(IRepository<>), typeof(MemoryRepository<>));

// Resolve
var repo = serviceProvider.GetService<IRepository<Product>>();

This pattern allows you to build flexible, testable applications with minimal boilerplate.

Common Pitfalls with Generic Interfaces

While generic interfaces are powerful, they can be misused:

Avoid making interfaces too generic-if the behavior doesn’t depend on the type, don’t make it generic. Also, be cautious with variance-it only works in specific scenarios and can lead to subtle bugs if misunderstood.

Don’t forget to implement all members of the interface. If you miss one, the compiler will show an error, but it’s easy to overlook in large interfaces.

Use generic interfaces to abstract type-dependent behavior. Keep them focused, document their purpose, and prefer interface-based programming for flexibility and testability.

Summary

Generic interfaces combine the power of generics with the structure of object-oriented design. They allow you to define contracts that work across types, enabling polymorphism, extensibility, and clean architecture. You’ve learned how to define and implement generic interfaces, how they support variance, and how they’re used in real-world scenarios like LINQ and dependency injection. In the next article, we’ll explore Covariance and Contravariance in more depth-understanding how type substitution works and how to use it safely in your generic designs.