【问题标题】:Named arguments and generic type inference in C# 4.0C# 4.0 中的命名参数和泛型类型推断
【发布时间】:2011-09-26 10:26:40
【问题描述】:

我的编程假设是,在 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);
}

顺便说一句,我观察到非静态方法的行为相同。我只是选择使用静态方法来使这里的示例更短一些。

【问题讨论】:

  • 听起来像是 Resharper 的问题...
  • 哦,埃里克! Eeeeeeeeeeeeric Lippert,请过来解释一下!
  • 我们应该引入一个“for:eric”标签吗?
  • 作为参考,我尝试使用安装在 Ubuntu 上的 Mono 2.6.7 C# 编译器来编译您的示例。它编译问题行,没有警告或错误。

标签: c# generics c#-4.0 compiler-errors type-inference


【解决方案1】:

推理不是在编译中的许多嵌套级别上起作用的东西。这是一种基于提供的参数的猜测。我觉得编译器作者没有考虑推断逻辑和命名参数。如果考虑抽象语法树,即使逻辑相同,但两者 F(()=>"xyz") 和 F(f:()=>"xyz") 从编译器的角度来看,是不同的抽象语法树。

我觉得这只是编译器设计者遗漏的一条规则,甚至编译器本身也是一个包含大量规则的程序。一条规则匹配第一种情况,但没有规则匹配第二种情况。它在概念上可能是正确的,但编译器只是一个程序,所有规则都是人工编码的。

好的,我想正如其他人已经确定的那样,这是一个错误,应该向微软报告!

【讨论】:

  • 它应该被视为 OP 所说的错误:冗余命名参数不应该破坏代码。
  • 我已将其报告为现在连接上的错误 (ID: 678498)。如果 Microsoft 确认这是一个错误,我将接受“这是一个错误”作为答案。
  • @BirgerH 你有错误报告的链接以便我们关注它吗?
  • 我试图发布完整链接,但似乎不允许这样做。 connect dot microsoft dot com slash VisualStudio slash 反馈 slash details slash 678498
  • 三个多月后,微软在他们的连接页面(见上面的链接)上承认这实际上是一个错误,它将在下一个版本中修复:“我们不再看到这个“在我们的 Visual Studio 内部版本中出现命名参数的问题,因此我们将这个错误解决为已修复。您将在 VS 2010 之后的 Visual Studio 版本中看到此修复。”因此我接受这个答案。
【解决方案2】:

只是想让您知道这一个特定于 C# 的错误(@leppie 我已经确认它在标准 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 中编译,但您已经指定了类型(字符串),那么推理问题在哪里?
  • @AkashKava:AFAICT 我已经复制了问题中的所有 C# 代码。当命名参数是需要类型推断的 lambda 表达式时,OP(我确认)C# 失败。这对应于我的 VB.NET 行:s = F(fn:=Function()"hello world"),工作正常。 @Weeble 的评论表明这是微软的错误。
  • 对不起,我想我错过了最后一行,因为我在 iPhone 上看到它,但不知道还有一行,因为 iPhone 不显示滚动条。
  • 我不明白你为什么把VB.Net带到这里。如果问题与框架有关,那么没关系。每种语言及其编译器都是独一无二的。仅仅因为这在 VB.Net 中有效,它不需要在 C# 中工作。两者都有不同的语义。
  • @ferosekhanj:是的,不同的编译器,但制造商相同,而且 VB 从 VB4 开始就有可选参数(至少),所以它事实上的标准。但是,是的,如果 VB.NET 中的效果相同,我的贡献会更重要(就像我在发布此答案的同一天通过this question 确认的那样)。
【解决方案3】:

调用带命名参数的函数和不带命名参数的函数是不一样的。在命名参数的情况下,编译器采用不同的路径,因为它需要首先解析命名参数。在您的情况下,编译器试图在解析 F 中的 T 之前找出参数 f,因此它要求程序员明确说明这一点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多