Understanding C# covariance and contravariance:
- Understanding C# covariance and contravariance (1) Delegates
- Understanding C# covariance and contravariance (2) Interfaces
- Understanding C# covariance and contravariance (3) Samples
- Understanding C# covariance and contravariance (4) Arrays
- Understanding C# covariance and contravariance (5) Higher-order functions
- Understanding C# covariance and contravariance (6) Typing issues
- Understanding C# covariance and contravariance (7) CLR
- Understanding C# covariance and contravariance (8) Void
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
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
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
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.