Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A covariant type parameter in C# allows a generic interface or delegate to use a more derived (specific) type than the one specified by the generic type argument. Denoted by the out contextual keyword, covariance preserves the assignment compatibility of generic types, enabling implicit reference conversions from Generic<Derived> to Generic<Base>.

Syntax and Declaration

Covariance is applied at the type parameter declaration level using the out modifier. It is strictly limited to generic interfaces and generic delegates.
// Covariant generic interface
public interface IProducer<out T>
{
    T Produce();
    
    // Properties can be covariant if they are read-only
    T CurrentItem { get; } 
}

// Covariant generic delegate
public delegate T FactoryDelegate<out T>();

Structural Mechanics

When a type parameter T is marked as covariant (out T), the C# compiler enforces strict positional validation. The type T is restricted to output positions only. However, the definition of an output position includes mathematical resolution of nested variance. Permitted (Output Positions):
  • Method return types.
  • get accessors of properties.
  • Nested within contravariant types in input positions: A covariant type parameter can appear in a method parameter list if it is nested inside a contravariant generic type (e.g., Action<T>). Because an input position of an input position mathematically resolves to an output position, this is structurally valid.
Prohibited (Input Positions):
  • Direct method parameters (e.g., void Consume(T item)).
  • set or init accessors of properties.
  • Generic constraints on methods within the interface.
  • ref, in, or out method parameters.
  • Nested within covariant types in input positions (e.g., void Consume(IEnumerable<T> items)).
If a covariant type parameter resolves to an input position, the compiler generates error CS1961.
public interface IVarianceMechanics<out T>
{
    // VALID: Direct output position.
    T GetItem(); 

    // VALID: Nested inside a contravariant type (Action<in T>).
    // An input position of an input position resolves to an output position.
    void RegisterCallback(Action<T> callback); 

    // ERROR CS1961: 'T' cannot be used as a direct input parameter.
    void Consume(T item); 

    // ERROR CS1961: 'T' cannot be used in a writable property.
    T MutableItem { get; set; } 
}

Type System Rules and Constraints

  1. Reference Types Only: Covariance in C# is supported exclusively for reference types. It relies on implicit reference conversions at the CLR level. Value types (structs, enums, primitives like int or double) have different memory layouts and cannot be variant.
public interface ISource<out T> { T GetItem(); }
public class Source<T> : ISource<T> { public T GetItem() => default!; }

public void DemonstrateVariance()
{
    ISource<string> stringSource = new Source<string>();
    
    // Valid: string and object are reference types.
    ISource<object> objectSource = stringSource; 

    ISource<int> intSource = new Source<int>();
    
    // Invalid: int is a value type. Compilation error CS0266.
    // ISource<object> boxedSource = intSource; 
}
  1. Interface and Delegate Exclusivity: The out modifier cannot be applied to type parameters of classes or structs. Variance is a feature of the abstraction (interfaces/delegates), not the implementation.
// ERROR: Invalid variance modifier. Only interfaces and delegates can be variant.
public class CovariantClass<out T> { } 
  1. Method Overloading: While covariant type parameters cannot be used as direct method parameters, their ability to be nested within contravariant types means they can be used to differentiate method overloads. The compiler successfully resolves overloads based on the distinct contravariant delegate or interface signatures wrapping the covariant type parameter.
public interface IEventSource<out T>
{
    // Valid method overloading using a covariant type parameter
    // nested within different contravariant types.
    void Subscribe(Action<T> handler);
    void Subscribe(Func<T, bool> conditionalHandler);
}

Assignment Compatibility

The primary mechanical effect of a covariant type parameter is altering the compiler’s type-checking rules for assignment. If TypeD derives from TypeB, and I<out T> is covariant, then I<TypeD> is considered a subtype of I<TypeB>.
public class BaseType { }
public class DerivedType : BaseType { }

public class Producer<T> : IProducer<T>
{
    public T Produce() => default!;
    public T CurrentItem => default!;
}

public void AssignCovariantTypes()
{
    // Instantiation uses the exact type
    IProducer<DerivedType> derivedInstance = new Producer<DerivedType>();

    // Covariant assignment: IProducer<DerivedType> is implicitly cast to IProducer<BaseType>
    IProducer<BaseType> baseInstance = derivedInstance;
}
Master C# with Deep Grasping Methodology!Learn More