我有以下类和函数:
class Foo { }
class Bar { }
class Baz { }
static T2 F<T1, T2>(Func<T1, T2> f) { return default(T2); }
static T3 G<T1, T2, T3>(Func<T1, Func<T2, T3>> f) { return default(T3); }
现在考虑以下示例:
// 1. F with explicit type arguments - Fine
F<Foo, Bar>(x => new Bar());
// 2. F with implicit type arguments - Also fine, compiler infers <Foo, Bar>
F((Foo x) => new Bar());
// 3. G with explicit type arguments - Still fine...
G<Foo, Bar, Baz>(x => y => new Baz());
// 4. G with implicit type arguments - Bang!
// Compiler error: Type arguments cannot be inferred from usage
G((Foo x) => (Bar y) => new Baz());
最后一个示例产生编译器错误,但在我看来它应该能够毫无问题地推断出类型参数。
问题:为什么编译器在这种情况下无法推断<Foo, Bar, Baz>
?
UPDATE:我发现简单地将第二个lambda包装在一个标识函数中会导致编译器正确地推断出所有类型:
static Func<T1, T2> I<T1, T2>(Func<T1, T2> f) { return f; }
// Infers G<Foo, Bar, Baz> and I<Bar, Baz>
G((Foo x) => I((Bar y) => new Baz()));
为什么它可以完美地完成所有单个步骤,而不是一次完成整个推理?编译器分析隐式lambda类型和隐式泛型类型的顺序是否存在一些细微之处?
答案 0 :(得分:18)
因为在这种情况下,C#规范中描述的算法不成功。让我们看看规范,看看为什么会这样。
算法描述冗长而复杂,所以我会大量缩写。
算法中提到的相关类型具有以下值:
Eᵢ
=匿名lambda (Foo x) => (Bar y) => new Baz()
Tᵢ
=参数类型(Func<T1, Func<T2, T3>>
)Xᵢ
=三种通用类型参数(T1
,T2
,T3
)首先,还有第一阶段,在你的情况下只做一件事:
7.5.2.1第一阶段
对于每个方法参数
Eᵢ
(在您的情况下,只有一个,lambda):
- 如果
Eᵢ
是匿名函数[它是],则显式参数类型推断(§7.5.2.7)从Eᵢ
到Tᵢ
- 否则,[不相关]
- 否则,[不相关]
- 否则,没有推断出这个论点。
我将在此处跳过显式参数类型推断的详细信息;对于电话G((Foo x) => (Bar y) => new Baz())
来说,它足以说明T1
= Foo
。
然后是第二阶段,它实际上是一个循环,试图缩小每个泛型类型参数的类型,直到它找到所有这些或放弃。一个重要的要点是最后一个:
7.5.2.2第二阶段
第二阶段如下:
- [...]
- 否则,对于具有相应参数类型
Eᵢ
的所有参数Tᵢ
,其中输出类型(第7.5.2.4节)包含不固定类型变量Xj
但输入类型(第7.5.2.3节)没有,输出类型推断(第7.5.2.6节)来自Eᵢ
到Tᵢ
。然后重复第二阶段。[翻译并应用于您的案例,这意味着:
- 否则,如果委托的返回类型(即
Func<T2,T3>
)包含尚未确定的类型变量(确实如此),但其参数类型(即T1
)没有(他们没有,我们已经知道T1
=Foo
),输出类型推断(制作§7.5.2.6。]
输出类型推断现在进行如下;再次,只有一个子弹点是相关的,这次是第一个:
7.5.2.6输出类型推断
输出类型推断通过以下方式从表达式
E
到类型T
进行 :
- 如果
E
是一个匿名函数[它是],推断返回类型为U
(第7.5.2.12节),T
是一个返回类型为Tb
的委托类型或表达式树类型{1}},然后下限推理(§7.5.2.9)从U
{{1> }}。- 否则,[rest snipped]
“推断返回类型”Tb
是匿名lambda U
,(Bar y) => new Baz()
是Tb
。提示下限推理。
我认为我现在不需要引用整个下限推理算法(它很长);它足以说它没有提到匿名函数。它负责继承关系,接口实现,数组协方差,接口和委托协变/反演,......但不是lambdas。因此,它的最后一个要点适用:
- 否则,不做推论。
然后我们回到第二阶段,因为没有对Func<T2,T3>
和T2
进行任何推断而放弃了。
故事的道德:类型推理算法不是lambda的递归。它只能从参数中推断出类型,并返回外部lambda的返回类型,而不是嵌套在它内部的lambdas。只有下限推理是递归的(因此它可以采用嵌套的通用结构,如T3
除外)但是输出类型推断(§7.5.2.6)也不是显式参数类型推断(第7.5.2.7节)是递归的,永远不会应用于内部lambdas。
当您添加对该识别函数List<Tuple<List<T1>, T2>>
的调用时:
I
然后类型推断首先应用于对G((Foo x) => I((Bar y) => new Baz()));
的调用,这会导致I
的返回类型被推断为I
。然后,外部lambda的“推断返回类型”Func<Bar, Baz>
是委托类型U
,Func<Bar, Baz>
是Tb
。因此,下限推理将成功,因为它将面临两个显式委托类型(Func<T2, T3>
和Func<Bar, Baz>
)但没有匿名函数/ lambdas。这就是识别功能使其成功的原因。
答案 1 :(得分:1)
lambda无法推断它的返回类型是什么,因为它没有被分配,并且无法由编译器确定。 查看此link有关lambdas返回类型的方式由编译器确定。 如果你愿意:
Func<Bar, Baz> f = (Bar y) => new Baz();
G((Foo x) => f);
然后编译器就能够根据它的分配来计算lambda的返回类型,但是从现在开始它没有分配给任何东西,编译器很难确定(Bar y) => new Baz();
的返回类型会的。
答案 2 :(得分:0)
对于编译器,lambda函数与Func不同,即对Func使用lambda函数意味着类型转换。在专门化泛型时,编译器不会执行“嵌套”类型转换。但是,在您的示例中需要这样做:
(Foo x) => (Bar y) => new Baz ()
的类型为lambda (Foo, lambda (Bar, Baz))
,但需要Func (T1, Func (T2, T3))
,即两次嵌套转换。