鉴于此代码:
class C
{
C()
{
Test<string>(A); // fine
Test((string a) => {}); // fine
Test((Action<string>)A); // fine
Test(A); // type arguments cannot be inferred from usage!
}
static void Test<T>(Action<T> a) { }
void A(string _) { }
}
编译器抱怨Test(A)
无法确定T
为string
。
这对我来说似乎是一个非常简单的案例,我发誓我在其他通用实用程序和扩展函数中依赖于更复杂的推理。我在这里缺少什么?
更新1:这是在C#4.0编译器中。我在VS2010中发现了这个问题,上面的示例来自我在LINQPad 4中制作的最简单的repro。
更新2:在可行的列表中添加了更多示例。
答案 0 :(得分:35)
Test(A);
这是失败的,因为唯一适用的方法(Test<T>(Action<T>)
)需要类型推断,类型推断算法要求每个参数都属于某种类型或者是匿名函数。 (这个事实是从类型推断算法(第7.5.2节)的规范推断出来的)方法组A
不属于任何类型(即使它可以转换为适当的委托类型),它不是一个匿名函数。
Test<string>(A);
这成功了,区别在于类型推断不是绑定Test所必需的,方法组A可以转换为所需的委托参数类型void Action<string>(string)
。
Test((string a) => {});
这成功了,不同之处在于类型推断算法在第一阶段(第7.5.2.1节)中提供了匿名函数。匿名函数的参数和返回类型是已知的,因此可以进行显式参数类型推断,从而在匿名函数(void ?(string)
)中的类型和委托类型中的类型参数之间建立相应的参数。 Test
方法的参数(void Action<T>(T)
)。没有为与匿名函数的此算法对应的方法组指定算法。
Test((Action<string>)A);
这成功了,区别在于将无类型方法组参数A
强制转换为类型,从而允许Test
的类型推断正常进行,并将特定类型的表达式作为唯一该方法的论据。
理论上我没理由为什么不能在方法组A
上尝试重载解析。然后 - 如果找到单个最佳绑定 - 方法组可以被赋予与匿名函数相同的处理。在这样的情况下尤其如此,其中方法组恰好包含一个候选项并且它没有类型参数。但它在C#4中不起作用的原因似乎是这个功能没有设计和实现。鉴于此功能的复杂性,应用程序的狭隘性以及三种简单的解决方法的存在,我不会为此屏住呼吸!
答案 1 :(得分:8)
我认为这是因为这是一个两步推理:
必须推断您要将A转换为通用委托
必须推断委托参数的类型应该是什么
我不确定这是否是原因,但我的预感是,对于编译器来说,两步推理并不一定容易。
只是预感,但有些事告诉我第一步就是问题。编译器必须计算转换为具有不同数量的泛型参数的委托 ,因此无法推断出参数的类型。
答案 2 :(得分:5)
这对我来说似乎是一个恶性循环。
Test
方法需要从泛型类型Action<T>
构造的委托类型参数。您传递了方法组:Test(A)
。这意味着编译器必须将您的参数转换为委托类型(method group conversion)。
但是哪种委托类型?要知道我们需要知道的委托类型T.我们没有明确指定它,因此编译器必须推断它以找出委托类型。
要推断方法的类型参数,我们需要知道方法参数的类型,在本例中是委托类型。编译器不知道参数类型,因此失败。
在所有其他情况下,任何一种论证都是显而易见的:
// delegate is created out of anonymous method,
// no method group conversion needed - compiler knows it's Action<string>
Test((string a) => {});
// type of argument is set explicitly
Test((Action<string>)A);
明确指定或类型参数:
Test<string>(A); // compiler knows what type of delegate to convert A to
答案 3 :(得分:2)
您正在传递方法的名称A. .Net框架可以将其转换为Action
,但它是隐含的,它不会对此负责。
但是,方法名称仍然是 NOT 明确的Action<>
对象。因此,它不会将类型推断为Action
类型。
答案 4 :(得分:2)
我可能错了,但我想C#无法推断类型的真正原因是由于方法重载和产生的歧义。例如,假设我有以下方法:void foo (int)
和void foo (float)
。现在,如果我写var f = foo
。编译器应选择哪个foo
?同样,使用Test(foo)
的示例也会出现同样的问题。