C# 泛型方法的类型推断
这里所谓的“泛型方法的类型推断”,指的是根据已有的方法实参的类型,推断出泛型方法的类型实参。例如一个泛型方法 void Method<T>(T[] args)
,如果我给出方法实参类型是 int[]
,那么希望能够推断出 T = int
。
这个问题是我在测试上一篇随笔《C# 使用 Binder 类自定义反射》中的类时发现的,当时为了能够让 PowerBinder 支持泛型方法绑定,完成了一些简单的类型推断工作,但是它只能支持直接使用泛型参数 T
作为参数类型,对于 T[]
,IList<T>
这种复杂一些的情况是不能处理的。
或者举个复杂点的例子,对于下面的泛型方法定义:
void Method<T>(IList<T> a, params T[] args);
再给出参数类型为:
{ typeof(IList<int>), typeof(int[]) } { typeof(IList<int[]>), typeof(int[]) } { typeof(IList<int[]>), typeof(int[][]) }
我希望能够正确的推断出 T
的类型分别为 int
、int[]
和 int[]
。
后来参考了《CSharp Language Specification》v5.0 中 7.5.2 类型推断一节,规范中给出了 C# 中进行类型推断的两阶段算法,算法分为两阶段主要是为了支持实参表达式和匿名函数的推断,而我的需求则要简单很多,只要支持普通的参数就可以了。又参考了 7.5.2.13 方法组转换的类型推断一节,最终得到了下面的简化算法。
首先对几个名词进行区分:类型形参、类型实参、方法形参和方法实参。
对于泛型方法定义
void Method<T>(T a)
,其中的T
是类型形参,T a
是方法形参。对于相应的封闭泛型方法的调用
Method<int>(10)
,其中的int
就是类型实参,10
就是方法实参。
泛型方法的类型推断,从形式上来定义,就是对给定泛型方法 Tr M<X1, …, Xn>(T1 x1, …, T_m x_m)
,其中 Tr
是返回值,X1, …, Xn
是类型形参,T1, …, T_m
是方法形参,和一个委托类型 D(U1 x1, …, U_m x_m)
,找到一组类型实参 S1, …, Sn
,使表达式 M<S1, …, Sn>
与 D
兼容(D
可由M<S1, …, Sn>
隐式转换而来)。
该算法首先认为所有 Xi
均未固定(即没有预设值),并从 D
的每个实参类型 Ui
到 M
的对应形参类型 Ti
进行下限推断(前提是 Ti
包含类型形参,即ContainsGenericParameters == true
),但是如果 xi
为 ref
或 out
形参,则从 Ui
到 Ti
进行精确推断。如果没有为任何 Xi
找到界限,则类型推断将失败。否则,所有将 Xi
均固定到对应的 Si
,它们是类型推断的结果。下面给出详细的推断算法,这里的算法经过了我的修改,与原规范并不完全相同。
一、精确推断
这里的精确推断指的是对于给定的实参类型 U
,找到合适的形参类型 V
,使得 U == V
。
按如下所述从类型 U
到类型 V
进行精确推断:
- 如果
V
是Xi
之一,则将U
添加到Xi
的精确界限集中。 -
否则,通过检查是否存在以下任何一种情况来确定集合
V1, …, V_k
和U1, …, U_k
:V
是数组类型V1[…]
,U
是具有相同秩的数组类型U1[…]
。V
是类型V1?
,U
是类型U1?
。V
是构造类型C<V1, …, V_k>
并且U
是构造类型C<U1, …, U_k>
。
如果存在以上任意情况,则从每个
Ui
到对应的Vi
进行精确推断。 - 否则,类型推断将失败。
二、下限推断
这里的下限推断指的是对于给定的实参类型 U
,找到合适的形参类型 V
,使得 V.IsImplicitFrom(U)
。
按如下所述从类型 U
到类型 V
进行下限推断:
- 如果
V
是Xi
之一,则将U
添加到Xi
的下限界限集中。 - 否则,如果
V
为V1?
类型,而U
为U1?
类型,则从U1
到V1
进行下限推断。 - 否则,如果
V
是数组类型V1[…]
,U
是具有相同秩的数组类型U1[…]
,或者V
是一个IEnumerable<V1>
、ICollection<V1>
或IList<V1>
,U
是一维数组类型U1[]
,如果不知道U1
是引用类型,则从U1
到V1
进行精确推断,否则进行下限推断。 - 否则,如果
V
是构造类、结构、接口或委托类型C<V1, …, V_k>
,并且存在唯一类型C<U1, …, U_k>
,使U
等于、(直接或间接)继承自或者(直接或间接)实现C<U1, …, U_k>
(“唯一性”限制表示对于interface C<T>{} class U: C<X>, C<Y>{}
,不进行从U
到C<T>
的推断,因为U1
可以是X
或Y
。),则从每个Ui
到对应的Vi
进行推断,如果不知道U1
是引用类型,则进行精确推断,否则推断依赖于C
的第i
个类型参数:- 如果该参数是协变的,则进行下限推断。
- 如果该参数是逆变的,则进行上限推断。
- 如果该参数是固定的,则进行精确推断。
- 否则,类型推断将失败。
三、上限推断
这里的上限推断指的是对于给定的实参类型 U
,找到合适的形参类型 V
,使得 U.IsImplicitFrom(V)
。
按如下所述从类型 U
到类型 V
进行上限推断:
- 如果
V
是Xi
之一,则将U
添加到Xi
的上限界限集中。 - 否则,如果
V
为V1?
类型,而U
为U1?
类型,则从U1
到V1
进行上限推断。 - 否则,如果
V
是数组类型V1[…]
,U
是具有相同秩的数组类型U1[…]
,或者V
是一维数组类型V1[]
,U
是一个IEnumerable<U1>
、ICollection<U1>
或IList<U1>
,如果不知道U1
是引用类型,则从U1
到V1
进行精确推断,否则进行上限推断。 - 否则,如果
U
是构造类、结构、接口或委托类型C<U1, …, U_k>
,V
是等于、(直接或间接)继承自或者(直接或间接)实现唯一类型C<V1, …, V_k>
的类、结构、接口或委托类型(“唯一性”限制表示如果我们有interface C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}
,则不进行从C<U1>
到V<Q>
的推断。也不进行从U1
到X<Q>
或Y<Q>
的推断。),则从每个Ui
到对应的Vi
进行推断,如果不知道U1
是引用类型,则进行精确推断,否则推断依赖于C
的第i
个类型参数:- 如果该参数是协变的,则进行上限推断。
- 如果该参数是逆变的,则进行下限推断。
- 如果该参数是固定的,则进行精确推断。
- 否则,类型推断将失败。
四、固定
固定是为了根据之前的算法得到的界限集,推断出类型参数的合适的值。
具有界限集的类型变量 Xi
按如下方式固定:
- 候选类型集
Ui
是在Xi
的界限集中的所有类型的集合。 - 然后我们依次检查
Xi
的每个界限:对于Xi
的每个精确界限U
,将与U
不同的所有类型Ui
都从候选集中移除(要求U == Ui
)。对于Xi
的每个下限U
,将不存在从U
进行的隐式转换的所有类型Ui
都从候选集中移除(要求Ui.IsImplicitFrom(U)
)。对于Xi
的每个上限U
,将不存在从其到U
进行的隐式转换的所有类型Ui
都从候选集中移除(要求U.IsImplicitFrom(Ui)
)。 - 如果在剩下的候选类型
Ui
中,存在唯一类型V
,该类型可由其他所有候选类型经隐式转换而来,则将Xi
固定到V
(也就是说,要求V
是其中最通用的类型)。 - 否则,类型推断将失败。
以上就是泛型方法的类型推断算法,其中只考虑了方法实参和方法形参一一对应的情况,如果需要处理 params T[]
参数,则需要对最后一个参数进行特殊处理,并分别使用 T
和 T[]
进行一次类型推断。做两次类型推断,就是为了判断是否是方法的展开形式的调用。
或者说,对于泛型方法定义
void Method<T>(T a, params T[] args);
如果参数为 { typeof(int), typeof(int[]) }
和 { typeof(int[]), typeof(int[]) }
,虽然 T[]
对应的实参是相同的,但推断出的 T
却是不同的,这就需要利用两次类型推断来处理。
这个算法的实现加上注释大概有 500 多行,这里就不再贴出,基本就是按照上面的 4 步来的,只是在一些细节上采用了更高效的做法。所有源码可以见这里。
作者:CYJB
出处:http://www.cnblogs.com/cyjb/
GitHub:https://github.com/CYJB/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。