Compile this simple program:
class Program
{
static void Foo( Action bar )
{
bar();
}
static void Main( string[] args )
{
Fo
EDIT: Eric Lippert's answer describes (much better) the issue - please see his answer for the 'real deal'
FINAL EDIT: As unflattering as it is for one to leave a public demonstration of their own ignorance in the wild, there's no gain in veiling ignorance behind a push of the delete button. Hopefully someone else can benefit from my quixotic answer :)
Thanks Eric Lippert and svick for being patient and kindly correcting my flawed understanding!
The reason that you are getting the 'wrong' error message here is because of variance and compiler-inference of types combined with how the compiler handles type resolution of named parameters
The type of the prime example
() => Console.LineWrite( "42" )
Through the magic of type inference and covariance, this has the same end result as
Foo( bar: delegate { Console.LineWrite( "42" ); } );
The first block could be either of type LambdaExpression
or delegate
; which it is depends on usage and inference.
Given that, is it no wonder that the compiler gets confused when you pass it a parameter that's supposed to be an Action
but which could be a covariant object of a different type?
The error message is the main key that points toward type resolution being the issue.
Let's look at the IL for further clues: All of the examples given compile to this in LINQPad:
IL_0000: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0005: brtrue.s IL_0018
IL_0007: ldnull
IL_0008: ldftn UserQuery.b__0
IL_000E: newobj System.Action..ctor
IL_0013: stsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0018: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_001D: call UserQuery.Foo
Foo:
IL_0000: ldarg.0
**IL_0001: callvirt System.Action.Invoke**
IL_0006: ret
b__0:
IL_0000: ldstr "42"
IL_0005: call System.Console.WriteLine
IL_000A: ret
Note the ** around the call to System.Action.Invoke
: callvirt
is exactly what it seems like: a virtual method call.
When you call Foo
with a named argument, you're telling the compiler that you're passing an Action
, when what you're really passing is a LambdaExpression
. Normally, this is compiled (note the CachedAnonymousMethodDelegate1
in the IL called after the ctor for Action
) to an Action
, but since you explicitly told the compiler you were passing an action, it attempts to use the LambdaExpression
passed in as an Action
, instead of treating it as an expression!
Short: named parameter resolution fails because of the error in the lambda expression (which is a hard failure in and of itself)
Here's the other tell:
Action b = () => Console.LineWrite("42");
Foo(bar: b);
yields the expected error message.
I'm probably not 100% accurate on some of the IL stuff, but I hope I conveyed the general idea
EDIT: dlev made a great point in the comments of the OP about the order of overload resolution also playing a part.