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 contravariant type parameter in C# allows a generic interface or delegate to accept a less derived (more general) type than the one explicitly specified. Denoted by the in keyword, contravariance reverses the standard assignment compatibility of generic types. If TypeB inherits from TypeA, contravariance enables an implicit reference conversion from IGeneric<TypeA> to IGeneric<TypeB>.

Syntax and Declaration

Contravariance is declared by placing the in contextual keyword before the type parameter in the angle brackets of an interface or delegate declaration.
// Contravariant interface
public interface IConsumer<in T>
{
    void Consume(T item);
}

// Contravariant delegate
public delegate void ActionHandler<in T>(T target);

Type System Mechanics

In a standard, invariant generic type, IGeneric<Base> and IGeneric<Derived> share no inheritance relationship, regardless of the relationship between Base and Derived. Contravariance alters the compiler’s type-checking rules to permit safe downward assignments.
public class Base { }
public class Derived : Base { }

public class Consumer<T> : IConsumer<T>
{
    public void Consume(T item) { }
}

// Instantiating with the less derived type (Base)
IConsumer<Base> baseConsumer = new Consumer<Base>();

// Valid implicit conversion due to the 'in' keyword
// IConsumer<Base> is assigned to IConsumer<Derived>
IConsumer<Derived> derivedConsumer = baseConsumer; 

// The compiler guarantees safety because derivedConsumer.Consume() 
// will only ever be passed a Derived object, which is a valid Base object.
derivedConsumer.Consume(new Derived());

Compiler Constraints and Rules

To guarantee type safety, the C# compiler enforces strict structural rules on how a contravariant type parameter (in T) can be utilized within the interface or delegate definition:
  1. Input Positions Only: The type parameter T can only be used in contravariant (input) positions. It may appear as a method parameter type or a write-only property type.
public interface IValidContravariant<in T>
{
    void Process(T input); // Valid: T is an input
    T WriteOnlyProperty { set; } // Valid: T is an input
}
  1. Prohibited Output Positions: The type parameter T cannot be used as a method return type, a read-only property, or a read-write property. Returning a contravariant type violates type safety because the caller expects a specific derived type, but the underlying implementation might return a base type.
public interface IInvalidContravariant<in T>
{
    T GetItem(); // Compiler Error: T cannot be a return type
    T ReadWriteProperty { get; set; } // Compiler Error: T cannot be in an output position
}
  1. Reference Types Only: Variance in C# is implemented via reference conversions in the CLR. Therefore, contravariance only supports reference types. It does not apply to value types (structs or primitives) because value types do not share the same memory representation.
IConsumer<object> objectConsumer = new Consumer<object>();

// Valid: string is a reference type
IConsumer<string> stringConsumer = objectConsumer; 

// Compiler Error: int is a value type; variance does not apply
IConsumer<int> intConsumer = objectConsumer; 
  1. Interfaces and Delegates Only: The in modifier can only be applied to generic interfaces and generic delegates. It cannot be applied to generic classes, structs, or records.
Master C# with Deep Grasping Methodology!Learn More