479 words
2 minutes
Understanding C# Covariance And Contravariance (6) Typing issues

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:

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 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.

Understanding C# Covariance And Contravariance (6) Typing issues
https://dixin.github.io/posts/understanding-csharp-covariance-and-contravariance-6-typing-issues/
Author
Dixin
Published at
2009-09-01
License
CC BY-NC-SA 4.0