ver*_*ald 22 c# type-inference nested-generics
当我遇到一个我不理解的类型推断错误时,我正在玩一个爱好项目.我把它简化为以下简单的例子.
我有以下类和函数:
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); }
Run Code Online (Sandbox Code Playgroud)
现在考虑以下示例:
// 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());
Run Code Online (Sandbox Code Playgroud)
最后一个示例产生编译器错误,但在我看来它应该能够毫无问题地推断出类型参数.
问题:为什么编译器<Foo, Bar, Baz>在这种情况下无法推断?
更新:我发现简单地将第二个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()));
Run Code Online (Sandbox Code Playgroud)
为什么它可以完美地完成所有单个步骤,而不是一次完成整个推理?编译器分析隐式lambda类型和隐式泛型类型的顺序是否存在一些微妙之处?
Tim*_*mwi 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,则下限推理(§7.5.2.9)由从U到Tb.- 否则,[休息剪断]
"推断的返回类型" U是匿名的lambda (Bar y) => new Baz()而且Tb是Func<T2,T3>.提示下限推断.
我认为现在不需要引用整个下界推理算法(它很长); 它足以说它没有提到匿名函数.它负责继承关系,接口实现,数组协方差,接口和委托协变/反演,......但不是lambdas.因此,它的最后一个要点适用:
- 否则,不做任何推论.
然后,我们再回到第二阶段,因为没有推论已经作出了其放弃T2和T3.
故事的道德:类型推理算法不是lambda的递归.它只能从参数中推断出类型,并返回外部lambda的返回类型,而不是嵌套在它内部的lambdas.只有下限推理是递归的(因此它可以采用嵌套的通用结构,例如,List<Tuple<List<T1>, T2>>除了)但是输出类型推断(第7.5.2.6节)和显式参数类型推断(第7.5.2.7节)都不是递归的,并且永远不会应用于内部lambdas .
当您向该识别功能添加调用时I:
G((Foo x) => I((Bar y) => new Baz()));然后类型推断首先应用于调用I,这导致I返回类型被推断为Func<Bar, Baz>.然后U外部lambda 的"推断返回类型" 是委托类型Func<Bar, Baz>而且Tb是Func<T2, T3>.因此,下限推理将成功,因为它将面临两个显式委托类型(Func<Bar, Baz>和Func<T2, T3>)但没有匿名函数/ lambdas.这就是识别功能使其成功的原因.
| 归档时间: |
|
| 查看次数: |
1602 次 |
| 最近记录: |