I have the following code, taken from this MSDN:
public class First { }
public class Second : First { }
public delegate First SampleDelegate(Second a);
// Matching signature.
public static First ASecondRFirst(Second first)
{ return new First(); }
// The return type is more derived.
public static Second ASecondRSecond(Second second)
{ return new Second(); }
// The argument type is less derived.
public static First AFirstRFirst(First first)
{ return new First(); }
// The return type is more derived
// and the argument type is less derived.
public static Second AFirstRSecond(First first)
{ return new Second(); }
SampleDelegate test;
test = ASecondRFirst;
test = ASecondRSecond;
test = AFirstRFirst;
test = AFirstRSecond;
This all compiles fine but I wanted to test assigning the delegate to an anonymous lambda expression:
test = (First x) => { return new Second(); };
However on that line I get the error:
Cannot convert lambda expression to type 'SampleDelegate' because the parameter types do not match the delegate parameter types
Namely: Parameter 1 is declared as type 'ConsoleApp1.First' but should be ConsoleApp1.Second'
("ConsoleApp1" is the name of the project).
I cannot understand what's wrong with my lambda.
Obviously covariance and contravariance and antivariance and whatnot are working OK, it seems to be just a problem with my lambda.
To be the boring person who gives the answer "because that's what the spec says" (TL;DR at the end with my thoughts)...
Basically, it's because method group conversions (e.g. assigning a method to a delegate) and anonymous function conversions (e.g. assigning a lambda to a delegate) follow different rules, and only the former benefit from variance.
(Note that a Method Group
means a group of 1 or more overloads of the same method - so your single methods still count as individual method groups)
Section 6.5 of the C# Language Specification talks about Anonymous function conversions:
An anonymous-method-expression or lambda-expression is classified as an anonymous function (§7.15). The expression does not have a type but can be implicitly converted to a compatible delegate type or expression tree type. Specifically, an anonymous function F is compatible with a delegate type D provided:
- ...
- If F has an explicitly typed parameter list, each parameter in D has the same type and modifiers as the corresponding parameter in F.
Section 6.6 however talks about Method group conversions:
An implicit conversion (§6.1) exists from a method group (§7.1) to a compatible delegate type. Given a delegate type D and an expression E that is classified as a method group, an implicit conversion exists from E to D if E contains at least one method that is applicable in its normal form (§7.5.3.1) to an argument list constructed by use of the parameter types and modifiers of D, as described in the following.
The compile-time application of a conversion from a method group E to a delegate type D is described in the following. Note that the existence of an implicit conversion from E to D does not guarantee that the compile-time application of the conversion will succeed without error.
- A single method M is selected corresponding to a method invocation (§7.6.5.1) of the form E(A), with the following modifications:
- The argument list A is a list of expressions, each classified as a variable and with the type and modifier (ref or out) of the corresponding parameter in the formal-parameter-list of D.
- The candidate methods considered are only those methods that are applicable in their normal form (§7.5.3.1), not those applicable only in their expanded form.
So the method group -> delegate conversion uses more or less the same rules as if you had tried to invoke the method with the corresponding parameter types. We're directed to Section 7.6.5.1, which directs us to Section 7.5.3.1. This gets complex, so I'm not going to paste it verbatim here.
Interestingly, I couldn't find the section on delegate covariance, only interface covariance (although section 6.6. says mention it in an example).
TL;DR, when you write:
SampleDelegate test = SomeMethodGroup;
the compiler goes through a whole algorithm to pick a suitable member of the method group which is compatible with the delegate type, following much the same rules as overload resolution if you were invoking the method.
When you write:
SampleDelegate test = (First first) => new Second();
the compiler follows a much simpler rule of "does the lambda's signature match the delegate signature".
I suppose this makes sense. Most of the time you're going to be writing:
SampleDelegate test = first => new Second();
and it's up to the compiler to figure out the parameter types from the delegate signature. If you add in explicit types yourself, that doesn't completely change the algorithm used: the compiler uses the same algorithm, but if the types it comes up with conflicts with your explicit types, you get an error.
Note that almost all of the time this doesn't matter. It's rare to put types on a lambda's parameters, so you'd normally just write this:
SampleDelegate test = x => new Second();
The compiler infers that x
actually a Second
, and that's fine: if you've written a lambda which can work if x
is a First
, it should also work if x
is a Second
(LSP notwithstanding). Notice that you're allowed to get away with returning a Second
even though SampleDelegate
returns a first: the compiler doesn't mind.