‘Delegate ‘System.Action’ does not take 0 arguments.’ Is this a C# compiler bug (lambdas + two projects)?

FINAL UPDATE:

The bug has been fixed in C# 5. Apologies again for the inconvenience, and thanks for the report.


Original analysis:

I can reproduce the problem with the command-line compiler. It certainly looks like a bug. It’s probably my fault; sorry about that. (I wrote all of the lambda-to-delegate conversion checking code.)

I’m in a coffee shop right now and I don’t have access to the compiler sources from here. I’ll try to find some time to reproduce this in the debug build tomorrow and see if I can work out what’s going on. If I don’t find the time, I’ll be out of the office until after Christmas.

Your observation that introducing a variable of type Action causes the problem to disappear is extremely interesting. The compiler maintains many caches for both performance reasons and for analysis required by the language specification. Lambdas and local variables in particular have lots of complex caching logic. I’d be willing to bet as much as a dollar that some cache is being initialized or filled in wrong here, and that the use of the local variable fills in the right value in the cache.

Thanks for the report!

UPDATE: I am now on the bus and it just came to me; I think I know exactly what is wrong. The compiler is lazy, particularly when dealing with types that came from metadata. The reason is that there could be hundreds of thousands of types in the referenced assemblies and there is no need to load information about all of them. You’re going to use far less than 1% of them probably, so let’s not waste a lot of time and memory loading stuff you’re never going to use. In fact the laziness goes deeper than that; a type passes through several “stages” before it can be used. First its name is known, then its base type, then whether its base type hierarchy is well-founded (acyclic, etc), then its type parameter constraints, then its members, then whether the members are well-founded (that overrides override something of the same signature, and so on.) I’ll bet that the conversion logic is failing to call the method that says “make sure the types of all the delegate parameters have their members known”, before it checks the signature of the delegate invoke for compatibility. But the code that makes a local variable probably does do that. I think that during the conversion checking, the Action type might not even have an invoke method as far as the compiler is concerned.

We’ll find out shortly.

UPDATE: My psychic powers are strong this morning. When overload resolution attempts to determine if there is an “Invoke” method of the delegate type that takes zero arguments, it finds zero Invoke methods to choose from. We should be ensuring that the delegate type metadata is fully loaded before we do overload resolution. How strange that this has gone unnoticed this long; it repros in C# 3.0. Of course it does not repro in C# 2.0 simply because there were no lambdas; anonymous methods in C# 2.0 require you to state the type explicitly, which creates a local, which we know loads the metadata. But I would imagine that the root cause of the bug – that overload resolution does not force loading metadata for the invoke – goes back to C# 1.0.

Anyway, fascinating bug, thanks for the report. Obviously you’ve got a workaround. I’ll have QA track it from here and we’ll try to get it fixed for C# 5. (We have missed the window for Service Pack 1, which is already in beta.)

Leave a Comment