15

As a means of introducing lazy formatting evaluation in a library I am developing, I have defined the delegates

public delegate string MessageFormatterDelegate(string message, params object[] arguments);
public delegate string MessageFormatterCallback(MessageFormatterDelegate formatterDelegate);

and something along the lines of the following class

public static class TestClass
{
    public static string Evaluate(MessageFormatterCallback formatterCallback)
    {
        return (formatterCallback(String.Format));
    }
}

However, this is behaving strangely enough: when running from an external project, the statement

Console.WriteLine(TestClass.Evaluate(message => message("{0},{1},{2}", 1, 2, 3)));

does not compile, failing with the error

Error   1   Delegate 'MessageFormatterDelegate' does not take 4 arguments

while

Console.WriteLine(TestClass.Evaluate((MessageFormatterDelegate message) => message("{0},{1},{2}", 1, 2, 3)));

compiles and works with no problems, printing 1,2,3 in the console. Why do I have to qualify the message argument with MessageFormatterDelegate type in the second lambda expression? Is there any way to circunvent this behaviour?

DotNetStudent
  • 889
  • 9
  • 24
  • Interesting. Does look like a bug of sorts. Have you test this with all the different C# compiler versions to see if it has been fixed? – leppie Feb 03 '12 at 11:53
  • Thanks leppie. In fact, I have tested it under Visual Studio 2010 Express Edition with SP1 freshly installed. – DotNetStudent Feb 03 '12 at 11:55
  • @DotNetStudent: And does it work there, or is that where it's failing for you? It should be fine - see my answer. – Jon Skeet Feb 03 '12 at 11:57
  • Sorry Jon. I forgot to add the "external project" part in the first post! In the same project it works, but not from an external project. – DotNetStudent Feb 03 '12 at 11:58
  • lambdas use implicit typing, it cannot translate your first attempt because you have provided it with an array of some integral type. if you had passed in "{0},{1},{2}", new object[] { 1, 2, 3 } it should have worked. Similarly, your explicit second lambda negates the necessity for implicit typing. (ahh, I missed the added external project detail..) – mtijn Feb 03 '12 at 11:59
  • @mtijn: BS! There is no generics involved here, so no type guessing... – leppie Feb 03 '12 at 12:12
  • 1
    @leppie: Inferring the lambda expression delegate type. ( (c) Jon Skeet :-P ) – mtijn Feb 03 '12 at 12:42
  • possible duplicate of ['Delegate 'System.Action' does not take 0 arguments.' Is this a C# compiler bug (lambdas + two projects)?](http://stackoverflow.com/questions/4466859/delegate-system-action-does-not-take-0-arguments-is-this-a-c-sharp-compiler) – Eric Lippert Feb 03 '12 at 16:30

2 Answers2

12

UPDATE:

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


This appears to be a duplicate of the known bug described here:

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

See my answer to that question for details.

It was also reported here:

C# Parser Bug on delegate?

This bug was my bad; I apologize for the error. We'll try to get a fix in C# 5.

If you think that you have actually found a different bug, please let me know and we'll start an investigation.

And thanks for the report, I appreciate it.

Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
7

EDIT: Okay, I've now got a much shorter example and a workaround.

First source file, External.cs:

public delegate string Callback(System.Action<string> x);

Second source file, Test.cs:

class Test
{
    static void Main()
    {
        Callback callback = action => action("hello");
    }
}

Compile with:

> csc /target:library External.cs
> csc Test.cs /r:External.cs

Error:

Delegate 'Action' does not take 1 arguments

Workaround: change the body of the Main method to:

Callback callback = action => action.Invoke("hello");

... or include the delegate declaration in the same assembly which uses it.

This definitely looks like a bug to me. When the compiler knows that the type of foo is a particular delegate type, then foo(arg) and foo.Invoke(arg) should be equivalent.

Will mail Eric Lippert...

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Sorry Jon. I forgot to add the "external project" part in the first post! In the same project it works, but not from an external project. – DotNetStudent Feb 03 '12 at 11:58
  • @DotNetStudent: So how much is in this "external" project? All but the call? I've a feeling I *may* have seen this before... – Jon Skeet Feb 03 '12 at 12:05
  • On this first project (for example, a DLL) is the definition of the delegates and of the `TestClass` class. On the second project (for example, a Console Application) are the `Console.WriteLine(TestClass...))` calls. – DotNetStudent Feb 03 '12 at 12:07
  • @DotNetStudent: Okay, will try to reproduce. – Jon Skeet Feb 03 '12 at 12:09
  • @JonSkeet: Type inference? I see no generics here. Why should type inference even come up in this conversation? – leppie Feb 03 '12 at 12:13
  • @leppie: Inferring the lambda expression delegate type. – Jon Skeet Feb 03 '12 at 12:18
  • @JonSkeet: Ok, I kinda agree, but with a known delegate type, it should already know what the type of the parameter is. – leppie Feb 03 '12 at 12:47
  • @leppie: You'd think - but apparently not. It's possible I shouldn't call this type inference - I'll check. – Jon Skeet Feb 03 '12 at 13:05
  • 1
    @leppie: Okay, I've checked - it's still called type inference when a method group conversion is involved (section 7.5.2.13) but I don't think it is for anonymous function conversion. – Jon Skeet Feb 03 '12 at 13:21
  • @JonSkeet: Too technical for me, seeing that it is weekend here already ;p – leppie Feb 03 '12 at 13:31
  • @JonSkeet: This 'cross assembly' bug does not actually surprise me. A long long time ago (before `InternalsVisibleTo`), I ran into a 'problem' where attributes on non-public fields would not be reflectable at all. – leppie Feb 03 '12 at 13:56
  • 1
    This appears to be a duplicate of the known bug described here: http://stackoverflow.com/questions/4466859/delegate-system-action-does-not-take-0-arguments-is-this-a-c-sharp-compiler/4467128 (See my answer to that question for details.) Jon, do you concur that this is a duplicate? If you think this is a different bug then I'll bring it to David's attention and we'll get a bug entered for C# 5. Thanks, and apologies for the error. – Eric Lippert Feb 03 '12 at 16:31
  • 1
    @EricLippert: Certainly looks like it. The workaround of explicitly calling `Invoke` may be an extra bit of information to record internally, if you haven't already got it. I guess it's probably worth checking against the C# 5 compiler, if that's meant to have fixed the other one :) (I can check against the VS 11 Preview when I get home if that would be useful, but not right now.) – Jon Skeet Feb 03 '12 at 16:34