做天写了一个矩阵运算类,我尝试了用泛型来实现,很明显泛型能在我给我的矩阵应用带来更多的选择.矩阵运算在游戏及其AI运用很广泛.不过后来我一想,还是没有必要运用泛型实现.不过昨天的经历让我有兴趣和大家讨论一下C#泛型参数的值运算问题:泛型参数带来的好处的同时也不能忽视其局限性.

泛型参数应用的一个很老的例子就是交换两个数的值了,比如下面的方法实现了两个任意值类型参数的值交换:

public void Swap(ref T a, ref T b)
{
    T temp = a;
    a = b;
    b = temp;
}

当然此方法不能应用于引用类型参数,为了防止泛型编程过程中,不恰当的调入应用参数增加隐性bug,C#对泛型增加了参数约束.在C#中参数约束是通过where关键字实现的,关于更多的泛型参数约束请参考MSDN
上面的方法中参数a,b肯定只能是值类型,所以修正程序第一行如下:

public void Swap(ref T a, ref T b) where T:struct

现在我们讨论的不是泛型的概念,不妨考虑一下以下问题:

  1. 能否通过泛型实现一个方法来返回所有参数中最大的那个呢?
  2. 能否通过泛型实现一个方法来得到一个泛型参数的倒数呢?
  3. 能否计算方法中泛型参数的绝对值,或者取负呢?

就拿第一个问题吧,很可能我们会写出下面的代码:

public T GetTheGreater(T a, T b) where T:struct
{
    if(a>b)
        return a;
    else
        return b;
}

世界上的事情总是有倒霉的一半,程序的第三行不会通过编译检查,已经很明了了,泛型参数是不能进行逻辑判断运算,至少不能进行大于的比较运算.实际上包括所有的逻辑运算和算术运算.由此可见上面的问题在C#里面几乎是不可解决了.
不过我们不要忘了倒霉的一半不是全部,C#3.0引入了表达式树,使得不同类型的值进行运算成为可能.表达式基类Expression提供了丰富的表达式运算方法来帮我们达到目的.我有点迫不及待的给出几行代码,下面是运用表达式树比较两个泛型参数的大小:

///
/// 判断a是否小于b
/// 注意参数顺序,判断的是前一个参数是否小于后一个参数
///
public static bool LessThan(T num1, T num2) where T:struct
{
    //参数表达式树节点1
    ParameterExpression para1 = Expression.Parameter(typeof(T), "left");
    //参数表达式树节点2
    ParameterExpression para2 = Expression.Parameter(typeof(T), "right");
    //两个参数节点构成关系为比较大小的二叉树节点
    BinaryExpression expsLessThan = Expression.LessThan(para1, para2);
    //定义lambda表达式
    Expression> exps = Expression.Lambda
        >(expsLessThan, new ParameterExpression[] { para1, para2 });
    //转化为匿名函数指针
    Func myFunction = exps.Compile();
    //返回执行结果
    return myFunction(num1, num2);
}

关于表达式树的更多概念请参见System.Linq.Expressions 命名空间这里不再重复MSDN的内容
当然上面的是逻辑判断的例子,如果是算术运算呢?我们只要改变对Expression代理的封装就是.

///
/// 将两个泛型参数做加法运算
///
public static T Add(T num1, T num2) where T:struct
{
    //参数表达式树节点1
    ParameterExpression para1 = Expression.Parameter(typeof(T), "left");
    //参数表达式树节点2
    ParameterExpression para2 = Expression.Parameter(typeof(T), "right");
    //两个参数节点构成关系为求和的二叉树节点
    BinaryExpression expsAdd = Expression.LessThan(para1, para2);
    //定义lambda表达式
    Expression> exps = Expression.Lambda
        >(expsLessThan, new ParameterExpression[] { para1, para2 });
    //转化为匿名函数指针
    Func myFunction = exps.Compile();
    //返回执行结果
    return myFunction(num1, num2);
}

上面的都是二元运算,如果是一元运算呢?有点点不同就是注意对lambda表达式的中的代理参数封装,还有就是二叉树节点为UnaryExpression(包含一元运算的表达式),而不是BinaryExpression(包含二元运算的表达式)
下面是一个求负的方法

///
/// 求给定泛型参数的负数
///
public static T Negate(T num) where T:struct
{
    //创建一个参数表达式树节点
    ParameterExpression para1 = Expression.Parameter(typeof(T), "left");
    //构成一个求负的一元表达式
    UnaryExpression expsNegate = Expression.Negate(p1);
    //定义lambda表达式
    Expression> exps = Expression.Lambda
        >(expsLessThan, new ParameterExpression[] { para1});
    //转化为匿名函数指针
    Func myFunction = exps.Compile();
    //返回执行结果
    return myFunction(num);
}

有了上面三个例子,基本上所有的逻辑运算和算术运算都不是问题了,不过我们如果在定义的泛型类中一个个定义这些方法肯定很别扭,不仅破坏泛型类本身的聚合性,而且重复写也麻烦,所以我们可以将这些运算方法封装为另外一个泛型类,比如取个名字就叫 CalculateGeneric,实际上我就是这么叫的.

///
/// 泛型数值类型计算类
///
public static class CalculateGeneric where T:struct
{
    /// 返回此数值泛型的0值
    public static T Zero;

    /// 实现两个泛型变量的相加算术运算
    public static T Add(T num1, T num2);

    /// 判断两个泛型变量值是否相等
    public static bool Equal(T num1, T num2);

    /// 取倒数
    public static T Reciprocal(T num);

    ///更多丰富的计算类型....
}

你可能会注意到上面的类中有一个访问器是返回0值,另外你还可能注意到取倒数的方法.所以我还要补充一个问题就是怎样解决泛型参数常量1和0的表示,比如我要通过泛型方法得到不同类型数值的倒数,我就要得到数值1.Expression类也没有提供取倒数的表达式运算方法.
我还没有很好的办法来解决这个问题,不过我临时是这样来解决的:

我不能保证上面的解决方法适合所有情况,欢迎大家提出错误或者分享你的解决方案.
另外说明,不能乱用这种方法来对泛型操作,毕竟这样修改了泛型的本意.(2010-4-4 15:57:01更新)