views:

450

answers:

2

Some strange behavior with the C# 4.0 co- and contravariance support:

using System;

class Program {
  static void Foo(object x) { }
  static void Main() {
    Action<string> action = _ => { };

    // C# 3.5 supports static co- and contravariant method groups
    // conversions to delegates types, so this is perfectly legal:
    action += Foo;

    // since C# 4.0 much better supports co- and contravariance
    // for interfaces and delegates, this is should be legal too:
    action += new Action<object>(Foo);
  }
}

It's results with ArgumentException: Delegates must be of the same type.

Strange, isn't it? Why Delegate.Combine() (which is been called when performing += operation on the delegates) does not support co- and contravariance at runtime?

Moreover, I've found that BCL's System.EventHandler<TEventArgs> delegate type does not has contravariant annotation on it's generic TEventArgs parameter! Why? It's perfectly legal, TEventArgs type used only at input position. Maybe there is no contravariant annotation because of it nicely hides the bug with the Delegate.Combine()? ;)

p.s. All this affects the VS2010 RC and later versions.

+6  A: 

Covariance and contravariance specifies inheritance relation between generic types. When you have covariance & contravariance, the classes G<A> and G<B> may be in some inheritance relationship depending on what A and B is. You can benefit from this when calling generic methods.

However, the Delegate.Combine method is not generic and the documentation clearly says when the exception will be thrown:

ArgumentException- Both a and b are not null reference (Nothing in Visual Basic), and a and b are not instances of the same delegate type.

Now, Action<object> and Action<string> are certainly instances of a different delegate type (even though related via inheritance relationship), so according to the documentation, it will throw an exception. It sounds reasonable that the Delegate.Combine method could support this scenario, but that's just a possible proposal (obviously this wasn't needed until now, because you cannot declare inherited delegates, so before co/contra-variance, no delegates had any inheritance relationship).

Tomas Petricek
Yeah, I've right about documentation and `Delegate.Combine()` behavior... But about supporting this scenario: why your are talking about delegates inheritance? For my scenario generic parameters should has inheritance relationship, not delegate types itself. For example, subscribe to an `EventHandler<RoutedEventArgs`> with an `EventHandler<EventArgs>` delegate instance.
ControlFlow
More precisely, I should have been talking about subtyping (that is, when you can treat value of type `A` as a value of type `B`). Covariance and contravariance define subtyping relationship between generic delegates (based on a subtyping/inheritance relationship between their type parameters). You can convert any subtype (inherited) to a supertype (base or interface) and covariance between delegates uses the same conversion rule. However, that doesn't quite extend to `Delegate.Combine` which probably uses internals of the delegate given as an argument.
Tomas Petricek
+23  A: 

Long story short: delegate combining is all messed up with respect to variance. We discovered this late in the cycle. We're working with the CLR team to see if we can come up with some way to make all the common scenarios work without breaking backwards compatibility, and so on, but whatever we come up with will probably not make it into the 4.0 release. Hopefully we'll get it all sorted out in some service pack. I apologize for the inconvenience.

Eric Lippert
Eric your answers and especially their candidacy are very appreciated since very few of us (I would assume) has any idea of what happens internally at Microsoft with .NET and your information frequently makes the internals alot less of a black box to myself and the rest of the community here (obvious with your 28K rep).
Chris Marisic
@Chris: thanks! I'm all about making this whole process more transparent.
Eric Lippert