Strange Behaviour Using Delegates and Lambdas
Asked Answered
S

2

16

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?

Spandrel answered 3/2, 2012 at 11:46 Comment(8)
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?Sidelight
Thanks leppie. In fact, I have tested it under Visual Studio 2010 Express Edition with SP1 freshly installed.Spandrel
@DotNetStudent: And does it work there, or is that where it's failing for you? It should be fine - see my answer.Calorimeter
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.Spandrel
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..)Accompany
@mtijn: BS! There is no generics involved here, so no type guessing...Sidelight
@leppie: Inferring the lambda expression delegate type. ( (c) Jon Skeet :-P )Accompany
possible duplicate of 'Delegate 'System.Action' does not take 0 arguments.' Is this a C# compiler bug (lambdas + two projects)?Inapposite
C
8

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...

Calorimeter answered 3/2, 2012 at 11:56 Comment(13)
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.Spandrel
@DotNetStudent: So how much is in this "external" project? All but the call? I've a feeling I may have seen this before...Calorimeter
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.Spandrel
@DotNetStudent: Okay, will try to reproduce.Calorimeter
@JonSkeet: Type inference? I see no generics here. Why should type inference even come up in this conversation?Sidelight
@leppie: Inferring the lambda expression delegate type.Calorimeter
@JonSkeet: Ok, I kinda agree, but with a known delegate type, it should already know what the type of the parameter is.Sidelight
@leppie: You'd think - but apparently not. It's possible I shouldn't call this type inference - I'll check.Calorimeter
@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.Calorimeter
@JonSkeet: Too technical for me, seeing that it is weekend here already ;pSidelight
@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.Sidelight
This appears to be a duplicate of the known bug described here: #4467359 (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.Inapposite
@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.)Calorimeter
I
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.

Inapposite answered 3/2, 2012 at 16:34 Comment(0)
C
8

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...

Calorimeter answered 3/2, 2012 at 11:56 Comment(13)
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.Spandrel
@DotNetStudent: So how much is in this "external" project? All but the call? I've a feeling I may have seen this before...Calorimeter
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.Spandrel
@DotNetStudent: Okay, will try to reproduce.Calorimeter
@JonSkeet: Type inference? I see no generics here. Why should type inference even come up in this conversation?Sidelight
@leppie: Inferring the lambda expression delegate type.Calorimeter
@JonSkeet: Ok, I kinda agree, but with a known delegate type, it should already know what the type of the parameter is.Sidelight
@leppie: You'd think - but apparently not. It's possible I shouldn't call this type inference - I'll check.Calorimeter
@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.Calorimeter
@JonSkeet: Too technical for me, seeing that it is weekend here already ;pSidelight
@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.Sidelight
This appears to be a duplicate of the known bug described here: #4467359 (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.Inapposite
@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.)Calorimeter

© 2022 - 2024 — McMap. All rights reserved.