SyntaxStudy
Sign Up
C# Generic Interfaces and Covariance
C# Beginner 1 min read

Generic Interfaces and Covariance

Generic interfaces like `IEnumerable`, `IList`, and `IReadOnlyCollection` are central to the .NET collection system. Writing your own generic interfaces lets you create reusable contracts that are type-safe without sacrificing flexibility. Variance in generic interfaces allows you to assign `IEnumerable` to `IEnumerable` (covariance, marked with `out`) or `IComparer` to `IComparer` (contravariance, marked with `in`). Covariance is read-only; contravariance is write-only in terms of the type parameter. Interface segregation — the I in SOLID — says that clients should not be forced to depend on methods they do not use. Prefer many small, focused interfaces (e.g. `IReadable`, `IWritable`) over one large interface, and compose them in implementing types as needed.
Example
// Generic interfaces, covariance, and interface segregation

// Generic repository interface
public interface IRepository<T> where T : class
{
    T?           GetById(int id);
    IList<T>     GetAll();
    void         Add(T entity);
    void         Remove(int id);
}

// Covariant interface — T can only appear as output (out)
public interface IProducer<out T>
{
    T Produce();
}

// Contravariant interface — T can only appear as input (in)
public interface IConsumer<in T>
{
    void Consume(T item);
}

// Demonstrating covariance assignment
public class AnimalProducer : IProducer<string>
{
    public string Produce() => "Cat";
}

IProducer<string> stringProducer = new AnimalProducer();
IProducer<object> objectProducer = stringProducer;  // covariance — works!
Console.WriteLine(objectProducer.Produce());         // "Cat"

// Interface segregation — split large interface
public interface IReadable<T>  { T Read(int id); }
public interface IWritable<T>  { void Write(T item); }
public interface IReadWrite<T> : IReadable<T>, IWritable<T> { }

// A read-only view can depend on just IReadable
public class ReportGenerator<T>
{
    private readonly IReadable<T> _store;
    public ReportGenerator(IReadable<T> store) => _store = store;
    public void Generate(int id) => Console.WriteLine(_store.Read(id)?.ToString());
}