“表达式树”配合“泛型参数字典”定义通用操作
2009-11-13 13:53 by 老赵, 18489 visits上午有朋友提出了这么一个问题:如何定义一个通用的相加操作。这样的话,我们就可以定义如下的扩展方法了:
public static class EnumerableExtensions { public static T Sum<T>(this IEnumerable<T> source) { T sum = default(T); bool first = true; foreach (var item in source) { if (first) { sum = item; first = false; } else { sum += item; } } return sum; } }
这个扩展方法的作用便是计算出source中所有的T类型元素之和——显然,上面标红的那行代码是无法编译通过的,因为并非所有的类型都有“相加”操作。因此,我们要设法实现这个功能。这又是“泛型参数字典”的用武之地了,当然更关键的其实是“表达式树”的“编译”功能:
public static class AddOperation<T> { static AddOperation() { var x = Expression.Parameter(typeof(T), "x"); var y = Expression.Parameter(typeof(T), "y"); var add = Expression.Add(x, y); var lambda = Expression.Lambda<Func<T, T, T>>(add, x, y); s_add = lambda.Compile(); } private static Func<T, T, T> s_add; public static T Add(T x, T y) { return s_add(x, y); } }
无论是什么类型,“相加”操作的表达式都是“Add”,因此从表达式树的角度来说它们是完全相同的。因此,我们只要使用表达式树进行Compile得到的结果,自然可以应对各种情况。很显然,性能也是非常高的——这是“泛型参数字典”和“表达式树”共同努力的结果。那么来尝试一下:
public struct Complex { public int m_real; public int m_imaginary; public Complex(int real, int imaginary) { this.m_real = real; this.m_imaginary = imaginary; } public static Complex operator +(Complex c1, Complex c2) { return new Complex(c1.m_real + c2.m_real, c1.m_imaginary + c2.m_imaginary); } public override string ToString() { return (String.Format("{0} + {1}i", m_real, m_imaginary)); } } // 调用 AddOperation<int?>.Add(3, 5); AddOperation<double>.Add(3.5, 6.8); AddOperation<Complex>.Add(new Complex(1, 2), new Complex(2, 3));
可见,无论是Nullable Int32,Double还是使用自定义“+”操作符的Complex类型,AddOperation类都能正常工作。只可惜字符串不行——因为编译器其实是将字符串相加编译为String.Concat方法的调用,而String类型本没有“相加”操作。因此,我们AddOperation需要进行修改,补充一个特殊情况:
static AddOperation() { if (typeof(T) == typeof(string)) { Func<string, string, string> strAdd = (x, y) => x + y; s_add = (Func<T, T, T>)(object)strAdd; } else { var x = Expression.Parameter(typeof(T), "x"); var y = Expression.Parameter(typeof(T), "y"); var add = Expression.Add(x, y); var lambda = Expression.Lambda<Func<T, T, T>>(add, x, y); s_add = lambda.Compile(); } }
如果T是字符串,那么我们“强行”指定一个加法的实现,至于其他情况,那还是最最普通的Add操作了。那么,还有哪些类型是需要特殊处理的呢?嗯……我还想到了“委托”……还有吗?
于是乎,我们的通用Sum方法便可以这样实现了:
public static T Sum<T>(this IEnumerable<T> source) { T sum = default(T); bool first = true; foreach (var item in source) { if (first) { sum = item; first = false; } else { sum = AddOperation<T>.Add(sum, item); } } return sum; }
感觉如何?是不是比.NET框架中定义的Enumerable.Sum扩展方法要强大许多呢?
表达式树,恩,为什么我没有用到呢?