In this post, you will see which generic types support covariance and contra-variance and why.
Variance in C# (covariance out and contravariance in) is mainly supported on:
- Generic interfaces
- Generic delegates
It is not supported on generic classes or structs.
Examples:
Interface supports covariance and contravariance
interface IProducer<out T> // covariance
{
T Get();
}
interface IConsumer<in T> // contravariance
{
void Put(T item);
}
interface IProducer<out T> // covariance
{
T Get();
}
interface IConsumer<in T> // contravariance
{
void Put(T item);
}
Delegate supports covariance and contravariance
delegate T Factory<out T>();
delegate void Processor<in T>(T value);
delegate T Factory<out T>();
delegate void Processor<in T>(T value);
Generic Class does not support covariance and contravariance
class Box<out T> // Compile-time error
{
}
class Box<out T> // Compile-time error
{
}
Generic Struct does not allow covariance and contravariance
struct Container<out T> // Compile-time error
{
}
struct Container<out T> // Compile-time error
{
}
Why classes/structs don't support variance?
Classes are usually both producers and consumers of T.
Example:
Example:
class Box<T>
{
public T Value { get; set; }
}
If covariance were allowed:
Box<Dog> dogs = new Box<Dog>();
Box<Animal> animals = dogs; // imagine this were legal
animals.Value = new Cat(); // valid for Box<Animal>
{
public T Value { get; set; }
}
If covariance were allowed:
Box<Dog> dogs = new Box<Dog>();
Box<Animal> animals = dogs; // imagine this were legal
animals.Value = new Cat(); // valid for Box<Animal>
Now dogs contains a Cat, which breaks type safety.
That is why C# restricts variance to interfaces/delegates where the compiler can enforce rules:
- out T → only output positions (return values)
- in T → only input positions (parameters)
Arrays are a special exception
Arrays are covariant even though they behave like classes:
Arrays are covariant even though they behave like classes:
string[] names = new string[2];
object[] objects = names; // allowed
objects[0] = 10; // Runtime exception
object[] objects = names; // allowed
objects[0] = 10; // Runtime exception
This compiles but throws:
ArrayTypeMismatchException
ArrayTypeMismatchException
Generic variance was designed to avoid such runtime problems and provide type safety.
So the rule is:
| Type | Covariance/Contravariance |
| Interface | supports |
| Delegate | supports |
| Class | does not support |
| Struct | does not allow |
| Array | supports (special case, runtime checked) |
Note. The generic type parameter itself does not have to be constrained as where T : class; the actual substituted type must be a reference type for variance conversion to occur.
No comments:
Post a Comment