C# generics and variance (using the out and in keywords) are incredibly powerful tools for creating flexible, reusable code. However, you might occasionally run into compiler errors that seem to defy logic, especially when mixing generic constraints with covariant interfaces.

In this article, we will explore why a covariant interface works perfectly with concrete, explicit types but fails inside a generic method, and how you can easily fix it.

The Problem: Covariance Works with Concrete Types, But Fails in Generics

Consider the following setup: a covariant interface IGetter<out T> and a concrete implementation DelegateGetter<T>.

interface IGetter<out T>
{
    T Get();
}

class DelegateGetter<T>(Func<T> f) : IGetter<T>
{
    public T Get() => f();
}

interface BaseInterface { }
sealed class LeafClass : BaseInterface { }

If you assign a DelegateGetter<LeafClass> to an IGetter<BaseInterface>, it compiles and works flawlessly because of covariance:

DelegateGetter<LeafClass> delegateGetter = new(() => new());
IGetter<BaseInterface> castToInterface = delegateGetter; // Works perfectly!

However, if you wrap this exact same logic inside a generic method with a type constraint (where T : U), the compiler throws a fit:

private static void CastGeneric<T, U>(DelegateGetter<T> delegateGetter) where T : U  
{
    IGetter<T> castToLeaf = delegateGetter; // Compiles  
    IGetter<U> castToInterface = delegateGetter; // Compiler Error!  
}

The compiler outputs the following error:

Cannot implicitly cast DelegateGetter<T> to IGetter<U>. An explicit conversion exists. (Are you missing a cast?)

Why Does This Happen?

The root of the issue lies in how the C# compiler handles implicit conversions and generic type parameters.

To convert DelegateGetter<T> to IGetter<U>, the compiler has to perform a two-step conversion:

  1. Convert the class DelegateGetter<T> to its implemented interface IGetter<T>.
  2. Apply covariance to convert IGetter<T> to IGetter<U> (since T : U).

When dealing with concrete, known types (like LeafClass and BaseInterface), the C# compiler is smart enough to bridge this gap. However, when dealing with open generic types (T and U), the compiler cannot guarantee this multi-step conversion implicitly. Because DelegateGetter<T> itself is a class—and classes in C# are strictly invariant—the compiler does not automatically chain the class-to-interface conversion with the covariant interface conversion.

The Solutions

Fortunately, resolving this issue is straightforward. Here are the two best ways to handle it.

Solution 1: Program to an Interface (Recommended)

The cleanest and most architecturally sound approach is to change the method signature to accept the interface IGetter<T> instead of the concrete class DelegateGetter<T>. This immediately tells the compiler that it is dealing with a covariant type.

private static void CastGeneric<T, U>(IGetter<T> delegateGetter) where T : U  
{
    IGetter<T> castToLeaf = delegateGetter; 
    IGetter<U> castToInterface = delegateGetter; // Compiles successfully!
}

Solution 2: Use an Explicit Intermediate Cast

If you absolutely must accept the concrete DelegateGetter<T> class as a parameter, you can help the compiler by explicitly casting it to its interface first. This breaks the conversion down into steps the compiler can easily verify:

private static void CastGeneric<T, U>(DelegateGetter<T> delegateGetter) where T : U  
{
    IGetter<T> castToLeaf = delegateGetter;  
    
    // Cast to IGetter<T> first, then the covariant transition to IGetter<U> happens implicitly
    IGetter<U> castToInterface = (IGetter<T>)delegateGetter; 
}

Summary

While C# covariance (out) works seamlessly with interfaces, classes remain invariant. When working inside generic methods, the compiler cannot implicitly chain a class-to-interface cast with a covariant interface conversion. By programming to interfaces or using an explicit intermediate cast, you can easily bypass this limitation and keep your generic code clean and type-safe.