我一直在编程,假设在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);
}
顺便说一下,我观察到了非静态方法的相同行为。我只是选择使用静态方法使这个例子缩短一点。
答案 0 :(得分:16)
推理不是可以在编译中的许多嵌套级别工作的东西。这是基于所提供的参数的猜测。我觉得编译器编写者没有考虑推断逻辑和命名参数。如果考虑抽象语法树,即使逻辑相同,但两者兼而有之 F(()=&gt; “中的xyz”) 和 F(F:()=&gt; “中的xyz”) 从编译器的角度来看是不同的抽象语法树。
我觉得这只是编译器设计者遗漏的规则,即使编译器本身也是一个包含大量规则的程序。一条规则匹配第一个案例但没有规则匹配第二个案例。它可能在概念上是正确的,但编译器只是一个程序,所有规则都是人为编码的。
好吧,我猜其他人已经确定,这是一个错误,应该报告给微软!!
答案 1 :(得分:6)
只是想让你知道这个是一个特定于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
答案 2 :(得分:-1)
调用具有命名参数且没有命名参数的函数是不一样的。在命名参数的情况下,编译器采用不同的路径,因为它需要首先解析命名参数。在你的情况下,编译器试图在解析F中的T之前找出参数f,因此它要求程序员明确说明。