57

I had been programming under the assumption that, when calling a method in C# 4.0, supplying names for your arguments would not affect the outcome unless in doing so you were "skipping" one or more optional parameters.

So I was a bit surprised to discover the following behavior:

Given a method which takes a Func<T>, executes it and returns the result:

public static T F<T>(Func<T> f)
{
    return f();
}

And another method from which the above method is visible:

static void Main()
{
    string s;

calling F (without named arguments) compiles without any issues:

    s = F<string>(() => "hello world"); // with explicit type argument <string>
    s = F(() => "hello world"); // with type inference

And when using a named argument...

    s = F<string>(f: () => "hello world");

... the above line of code using the explicit type argument still compiles without issues. And maybe not too surprisingly, if you have ReSharper installed it will suggest that the "Type argument specification is redundant".

However, when removing the type argument...

    s = F(f: () => "hello world");

the C# compiler will report this error:

The type arguments for method 'Program.F(System.Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Is there a logical explanation for this interaction between named arguments and type inference?

Is this behavior documented somewhere in the language specification?

I understand that it is not necessary at all for me to name the argument. However, I discovered this behavior in a much more complex scenario where I thought it might make sense to name the arguments in my method call for internal documentation purposes. I am not asking how to work around this issue. I am trying to understand some of the finer points of the language.

To make things more interesting I discovered that the following all compiles without issues:

    Func<string> func = () => "hello world";
    s = F<string>(func);
    s = F(func);
    s = F<string>(f: func);
    s = F(f: func);
}

By the way I have observed the same behavior with non-static methods. I just chose to use static methods to make the example here a bit shorter.

Mark Hurd
  • 10,665
  • 10
  • 68
  • 101
BirgerH
  • 748
  • 7
  • 8

3 Answers3

16

Inference is not something that will work at many nested levels in compilation. It is kind of a guess based on arguments supplied. I feel the compiler writers did not consider inferring logic along with named parameter. If you consider abstract syntax tree, Even though the logic is same, but both F(()=>"xyz") And F(f:()=>"xyz") Are different abstract syntax trees from compiler's perspective.

I feel it's just a rule missed by compiler designer where even the compiler itself is a program with huge set of rules. One rule matches first case but no rule matches second one. It may be conceptually right but compiler is just a program and all rules are human coded.

Ok, I guess as others have determined, its a bug and should be reported to Microsoft !!

Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • 2
    It ought to be seen as a bug as the OP stated: redundant named arguments shouldn't break code. – Mark Hurd Jul 05 '11 at 10:48
  • I have reported this as a bug on connect now (ID: 678498). If Microsoft confirms that it is a bug I will accept "it is a bug" as the answer. – BirgerH Jul 06 '11 at 06:17
  • @BirgerH do you have a link to the bug report so we can follow it? – Nahydrin Jul 06 '11 at 21:28
  • I was trying to post the full link but SO doesn't seem to allow it. connect dot microsoft dot com slash VisualStudio slash feedback slash details slash 678498 – BirgerH Jul 06 '11 at 21:53
  • 1
    After more than three months, Microsoft has acknowledged on their connect page (see link above) that this is in fact a bug and that it will be fixed in the next version: "We no longer see this issue with named arguments occurring in our internal builds of Visual Studio, and so we're resolving this bug as Fixed. You'll see this fix in the version of Visual Studio after VS 2010." I therefore accept this answer. – BirgerH Oct 27 '11 at 22:24
6

Just want to let you know this is a bug specific to C# (and @leppie I have confirmed it does fail with the standard csc.exe, not even in Visual Studio). Redundantly specifying a named argument shouldn't cause a change in behaviour at all.

The bug has been reported at Microsoft Connect.

The equivalent VB works fine (because it does compile I added a little bit to confirm the runtime behaviour is as expected):

Imports System

Module Test
  Function F(Of T)(ByVal fn As Func(Of T)) As T
    Return fn()
  End Function

  Function Inc(ByRef i as Integer) As String
    i += 1
    Return i.ToString
  End Function

  Sub Main()
    Dim s As String
    s = F(Of String)(Function()"Hello World.")
console.writeline(s)
    s = F(Function()"Hello, World!")
console.writeline(s)
    s = F(Of String)(fn:=Function()"hello world.")
console.writeline(s)
    s = F(fn:=Function()"hello world")
console.writeline(s)
    Dim i As Integer
    Dim func As Func(Of String) = Function()"hello world " & Inc(i)
    s = F(Of string)(func)
console.writeline(s)
    s = F(func)
console.writeline(s)
    s = F(Of string)(fn:=func)
console.writeline(s)
    s = F(fn:=func)
console.writeline(s)
  End Sub
End Module

Output:

Hello World.
Hello, World!
hello world.
hello world
hello world 1
hello world 2
hello world 3
hello world 4
Mark Hurd
  • 10,665
  • 10
  • 68
  • 101
  • I don't think you have understood the question, your last line compiles in vb but you have spcified the type already (of String) so where is question of inference? – Akash Kava Jul 04 '11 at 19:01
  • @AkashKava: AFAICT I have duplicated all the C# code in the question. The OP (and I confirmed) the C# fails when a named argument is a lambda expression where type inference is needed. This corresponds to my VB.NET line: `s = F(fn:=Function()"hello world")`, which works fine. @Weeble's comment suggests it is a Microsoft bug. – Mark Hurd Jul 05 '11 at 10:46
  • I am sorry I think I missed last line because I saw it on iPhone and didnt know there is one more line as iPhone does not show scrollbars. – Akash Kava Jul 05 '11 at 11:38
  • I don't understand why you bring VB.Net here. If the question was related to the the framework then its ok. Each language and its compiler are unique. Just because this works in VB.Net it need not work in C#. Both has different semantics. – ferosekhanj Jul 07 '11 at 06:26
  • @ferosekhanj: Yes, separate compilers, but same manufacturer, and VB has had optional parameters since VB4 (at least), so it _is_ the defacto standard. But yes, my contribution would have been more important if the effect _was_ the same in VB.NET (like I confirmed with [this question](http://stackoverflow.com/questions/407983/c-3-0-generic-type-inference-passing-a-delegate-as-a-function-parameter/6572884#6572884) on the same day I posted this answer). – Mark Hurd Jul 07 '11 at 10:08
  • Defacto standard? At Microsoft the VB and C# teams are separate teams, while they may work closely together in terms of compiler development, they also have different groups maintaining and working on the different specifications. So you cannot say it is the defacto standard, when they are different languages, different specifications, different teams, so on so forth. You should not state that this is a bug in C# because you can achieve this in VB, because C# has nothing to do with VB, and VB has nothing to do with C#. Just because Microsoft is the manufacturer of both is irrelevant. – David Anderson Jul 11 '11 at 06:48
  • @danderson: You're probably actually right, C# is likely to be more directly influenced by C++ or Java with any concepts of named arguments they may have, but they've added this functionality explicitly because of the use VB programmers get from it when accessing Microsoft COM objects like Word and Excel, so I would hope they saw VB as a de facto standard to consider. (NB I know they explicitly avoided this feature for a number of versions of C#, and this specific issue is unlikely to be relevant to COM interop.) – Mark Hurd Jul 11 '11 at 08:26
-1

Calling a function with named parameters and without named parameters are not the same. In case of named parameters compiler takes different path since it needs to resolve the named parameters first. In your case compiler is trying to figure out parameter f before resolving the T in F so it is requesting the programmer to explicitly state that.

ferosekhanj
  • 1,086
  • 6
  • 11