Understanding C# covariance and contravariance:
In each previous part, type implicit conversion is dicussed, which happen during the variances, like
- function (method / delegate) conversion, as well as higher-order function conversion;
- generic interface conversion;
- array conversion.
Since C# 4.0 introduces new variance rules, which means in C# 4.0, types could be more convertible than C# 2.0 / 3.0, there could be potential typing issues for C# 4.0. If comparing some code between Visual Studio 2008 (C# 2.0 / 3.0) and Visual Studio 2010 Beta2 (C# 4.0), you can find that is true.
Delegate covariance / contravariance issues
Take a look at this covariance sample:
Func<Base> baseFunc = () => new Base();
Func<Derived> derivedFunc = () => new Derived();
// This always prints "True".
Console.WriteLine(derivedFunc is Func<Derived>);
// Covariance is supported by C# 4.0.
// This prints "False" in C# 2.0 / 3.0, prints "True" in C# 4.0.
Console.WriteLine(derivedFunc is Func<Base>);
And this contravariance sample:
Action<Base> baseAction = arg => { };
Action<Derived> derivedAction = arg => { };
// This always prints "True".
Console.WriteLine(baseAction is Action<Base>);
// Contravariance is supported by C# 4.0.
// This prints "False" in C# 2.0 / 3.0, prints "True" in C# 4.0.
Console.WriteLine(baseAction is Action<Derived>);
Yes, in C# 4.0, delegate types are more convertable.
Interface covariance / contravariance issues
This is the interface covariance on IEnumerator<out T>:
IEnumerator<Base> baseEnumerator = new BaseEnumerator();
IEnumerator<Derived> derivedEnumerator = new DerivedEnumerator();
// This always prints "True".
Console.WriteLine(derivedEnumerator is IEnumerator<Derived>);
// Covariance is supported by C# 4.0.
// This prints "False" in C# 2.0 / 3.0, prints "True" in C# 4.0.
Console.WriteLine(derivedEnumerator is IEnumerator<Base>);
It looks acceptable.
Then this is covariance on IEnumerable<out T>:
IEnumerable<Base> bases = new Base[0];
IEnumerable<Derived> deriveds = new Derived[0];
// This always prints "True".
Console.WriteLine(deriveds is IEnumerable<Derived>);
// Covariance is supported by C# 4.0.
// This prints "True" in C# 2.0 / 3.0 / 4.0.
Console.WriteLine(deriveds is IEnumerable<Base>);
Looks like a big mess.
To try interface contravariance, IComparable<in T> can be used as an example:
internal class Base : IComparable<Base>
{
public int CompareTo(Base other)
{
throw new NotImplementedException();
}
}
internal class Derived : Base
{
}
Then:
IComparable<Base> baseComparable = new Base();
// This cannot compile in C# 2.0 / 3.0,
// because Derived does not implement IComparable<Derived>.
IComparable<Derived> derivedComparable = new Derived();
// This always prints "True".
Console.WriteLine(baseComparable is IComparable<Base>);
// Contravariance is supported by C# 4.0.
// This prints "False" in C# 2.0 / 3.0, prints "True" in C# 4.0.
Console.WriteLine(baseComparable is IComparable<Derived>);
The interface variances are also confusing.
Conclusion
C# 4.0 introduces new variance rules, so in C# 4.0, types becomes more convertible than C# 2.0 / 3.0. And this makes the same code work differently between C# 2.0 / 3.0 and C# 4.0.