C# 4.0中的命名参数和泛型类型推断

57

我一直在以这样的假设下进行编程:当在C# 4.0中调用方法时,除非你“跳过”了一个或多个可选参数,否则为参数命名不会影响结果。

所以当我发现以下行为时有点惊讶:

给定一个接受 Func<T>, 执行它并返回结果的方法:

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

以下是另一种可见以上方法的方法:

static void Main()
{
    string s;

调用F(不使用命名参数)编译时没有任何问题:

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

当使用命名参数时...

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

使用显示类型参数的上述代码行仍然可以编译而不会出现问题。也许并不奇怪,如果您安装了ReSharper,它会建议"类型参数规范是多余的"。

但是,当删除类型参数时......

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

C#编译器会报告以下错误:

无法从使用中推断方法“Program.F(System.Func)”的类型参数。尝试显式指定类型参数。

命名参数和类型推断之间有逻辑上的解释吗?

这种行为是否在语言规范中有所说明?

我知道根本没必要给参数命名。然而,在更复杂的场景下,我发现在方法调用中为了内部文档目的而命名参数可能是有意义的。我不是在询问如何解决此问题。我试图理解该语言的一些细节。

为了使事情更有趣,我发现以下所有内容都可以编译而没有问题:

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

顺便说一下,我发现非静态方法也有相同的行为。我只是选择使用静态方法来使这个例子更短。


看看这个:https://dev59.com/9XRC5IYBdhLWcg3wG9Nb - Slavo
听起来像是Resharper的问题... - leppie
11
哦,埃里克!埃里克·利珀特,请过来解释一下! - Ben Voigt
3
我们是否应该引入一个“for:eric”标签? - flq
为参考,我尝试使用安装在Ubuntu上的Mono 2.6.7 C#编译器编译您的示例。它可以编译问题代码行而没有任何警告或错误。 - Weeble
显示剩余2条评论
3个回答

16

推断并不适用于编译的多层嵌套。它基于所提供的参数猜测结果。我认为编译器的编写者没有考虑到推断逻辑和命名参数一起使用的情况。如果您考虑抽象语法树,即使逻辑相同,对于编译器而言, F(()=>"xyz") 和 F(f:()=>"xyz") 是不同的抽象语法树。

我认为这只是编译器设计者忽略的一个规则。即使编译器本身也是一个具有巨大规则集的程序。一条规则匹配第一种情况,但没有规则匹配第二种情况。这在概念上可能是正确的,但编译器只是一个程序,所有规则都是人工编码的。

好的,我想就像其他人确定的那样,这是一个bug,应该向Microsoft报告!!


2
它应该被视为一个 bug,正如 OP 所述:冗余的命名参数不应该破坏代码。 - Mark Hurd
我已经在Connect上报告了这个问题(ID:678498)。如果微软确认这是一个bug,我将接受“这是一个bug”的答案。 - BirgerH
@BirgerH 你有错误报告的链接吗,这样我们就可以跟进它了吗? - Nahydrin
我试图发布完整链接,但 SO 似乎不允许。连接点 microsoft 点 com 斜杠 VisualStudio 斜杠 feedback 斜杠 details 斜杠 678498。 - BirgerH
1
微软经过三个多月的时间,在他们的连接页面(见上面的链接)上承认这实际上是一个 bug,并且将在下一个版本中修复:“我们不再看到命名参数出现在 Visual Studio 的内部构建中,因此我们将此错误解决为已修复。您将在 VS 2010 之后的 Visual Studio 版本中看到此修复。”因此,我接受了这个答案。 - BirgerH

6

我想让您知道,这个问题只存在于C#中(经过我的确认,它在标准的csc.exe中失败,甚至在Visual Studio中也是如此)。冗余地指定命名参数不应该导致行为上的任何改变。

该问题已在Microsoft Connect上报告。

等效的VB代码可以正常工作(因为它可以编译,我添加了一点内容以确认运行时行为符合预期):

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

输出:

Hello World.
Hello, World!
hello world.
hello world
hello world 1
hello world 2
hello world 3
hello world 4

我认为你没有理解这个问题,你的最后一行在vb中编译,但是你已经指定了类型(String),那么推断的问题在哪里? - Akash Kava
据我所知,我已经复制了问题中的所有C#代码。 OP(我确认)当命名参数是需要类型推断的lambda表达式时,C#会失败。这对应于我的VB.NET行:s = F(fn:=Function()"hello world"),它可以正常工作。 @Weeble的评论表明这是一个Microsoft的错误。 - Mark Hurd
很抱歉,我认为我错过了最后一行,因为我在 iPhone 上看到它,不知道还有一行,因为 iPhone 不显示滚动条。 - Akash Kava
我不明白为什么你要提到VB.Net。如果问题与框架有关,那就没问题了。每种语言及其编译器都是独特的。仅仅因为在VB.Net中可行,并不意味着在C#中也可行,两者具有不同的语义。 - ferosekhanj
@ferosekhanj:是的,不同编译器,但同一制造商,自VB4(至少)以来,VB就有了可选参数,因此它是事实上的标准。但是,如果效果在VB.NET中是相同的(就像我在同一天确认的这个问题),那么我的贡献将更加重要。 - Mark Hurd
显示剩余2条评论

-1

使用命名参数和不使用命名参数调用函数是不同的。在使用命名参数的情况下,编译器需要先解析命名参数,因此会采取不同的路径。在您的情况下,编译器在解析 F 中的 T 之前尝试找到参数 f,因此请求程序员明确声明。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接