用dynamic增强C#泛型表达力

时间:2022-11-27 07:15:46




C++的泛型是基于模板(template)技术的。模板本身并不作为独立的编译单元,而是在编译时绑定实际参数类型进行模板实例化,类似与C语言的宏展开,在运行时并不存在独立的模板类型。模板对泛型参数的约束是基于操作的语法特征的,属于一种静态的duck typing机制,十分灵活。


下面的代码定义了一个泛型Add函数,它对泛型参数T的要求只是支持+运算,并不要求T是某个类的子类,或是实现了某个接口。int, double, std::string等支持+运算符的类型都可以成功匹配T。

 

//C++

template<typename T>
T Add(const T& t1, const T& t2) {
    return t1 + t2;
}

int main() {
    int i = Add(1, 2);
    double d = Add(1.1, 2.2);
    std::string s = Add(std::string("abc"), std::string("def"));

    std::cout << i << " " << d << " " << s << std::endl;

    return 0;
}


 

输出:

>>3 3.3 abcdef


 

而类似的代码在C#中却无法编译通过:

​​用dynamic增强C#泛型表达力​​


 

这是由于C#采用基于reification的泛型机制,泛型类会单独编译,并且在运行时存在;因此,C#对于泛型参数的要求更加严格,只能通过where关键字表达基于继承关系的约束,无法通过duck typing的方式表达类型约束。与模板相比,这种机制的好处在于可以更好的支持反射和元编程,但其缺点是泛型的表达能力不如模板。幸好C#4.0中引入了动态类型机制,我们可以通过动态类型来实现基于duck typing的泛型参数约束。

//C#

static class Calculator {
    public static T Add<T>(T t1, T t2) {
        dynamic d1 = t1;
        dynamic d2 = t2;

        return (T)(d1 + d2);
    }
}

public static void Main(string[] args){
    int i = Calculator.Add(1, 2);
    double d = Calculator.Add(1.1, 2.2);
    string s = Calculator.Add("abc", "def");

    Console.WriteLine(i + " " + d + " " + s);

}


 

输出:

>>3 3.3 abcdef

 

除了运算符重载,对于普通的方法调用也是适用的。这种方法是一种动态duck typing的泛型参数约束机制,依赖于运行时的方法查找,与模板编译时的检查不同,它需要使用者保证传入的对象符合相应要求。