在C#4.0中命名参数和泛型类型推断
我一直在进行编程,假设在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,它会提示“Type参数规范是多余的”。
但是,删除类型参数时...
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);
}
顺便说一下,我观察到与非静态方法相同的行为。 我只是选择使用静态方法来缩短这个例子。
推理不是在编译中的许多嵌套级别上工作的。 根据提供的参数,这是一种猜测。 我觉得编译器编写者并没有考虑用命名参数推理逻辑。 如果您考虑抽象语法树,即使逻辑相同,但是F(()=>“xyz”)和F(f:()=>“xyz”)从编译器的角度来看,它们是不同的抽象语法树。
我觉得这只是编译器设计师错过的一个规则,即使编译器本身也是一个拥有大量规则的程序。 一条规则匹配第一种情况,但没有规则匹配第二种情况 它可能在概念上是正确的,但编译器只是一个程序,所有规则都是人工编码的。
好吧,我想其他人已经确定,它的错误,并应该向微软报告!
只是想让你知道这是一个特定于C#的bug(和@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
使用命名参数调用一个没有命名参数的函数是不一样的。 在命名参数的情况下,编译器会采用不同的路径,因为它需要首先解析命名参数。 在你的情况下,编译器试图在解析F中的T之前计算出参数f,以便它请求程序员明确指出。
链接地址: http://www.djcxy.com/p/51777.html