Expression的妙用
到目前为止,我们只是观察了一个用C#就能完成的简单示例——这自然没什么大不了的。所以我们现在来看一些C#无法做到的东西……我经常被人问及,有什么 方法可以编写一个通用的“加法”操作,可以对任意类型进行计算。简而言之:没有这样的语法。不过我们可以使用Expression来做到这一点。
简单起见,我使用var关键字来代替显式变量类型,我们现在将要观察如何简单地缓存可重用的委托对象:
{
private static readonly Func<T, T, T> add;
public static T Add(T x, T y)
{
return add(x, y);
}
static Operator()
{
var x = Expression.Parameter(typeof(T), "x");
var y = Expression.Parameter(typeof(T), "y");
var body = Expression.Add(x, y);
add = Expression.Lambda<Func<T, T, T>>(
body, x, y).Compile();
}
}
因为我们在静态构造函数中编译委托对象,这样的操作只会为每个不同的类型T执行一次,接着便可重用了——所以对于任何代码我们现在都可以直接使用 Operator
这个做法甚至支持用户自定义类型——只要你为它定义了+运算符。如果没有合适的运算符,这段代码便会抛出异常。在实际使用过程中这不太会是个问题,如果你不放心的话,可以在Add外加上try语句进行保护。
MiscUtil类库提供了完整的通用操作符实现。
Expression支持哪些东西?
在.NET 3.5中,Expression支持完整的用于查询或创建对象的操作符。
- 算术运算:Add, AddChecked, Divide, Modulo, Multiply, MultiplyChecked, Negate, NegateChecked, Power, Subtract, SubtractChecked, UnaryPlus
- 对象创建:Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInit
- 二进制运算:And, ExclusiveOr, LeftShift (<<), Not, Or, RightShift (>>)
- 逻辑运算:AndAlso (&&), Condition (? :), Equal, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, NotEqual, OrElse (||), TypeIs
- 成员访问:ArrayIndex, ArrayLength, Call, Field, Property, PropertyOrField
- 其他:Convert, ConvertChecked, Coalesce (??), Constant, Invoke, Lambda, Parameter, TypeAs, Quote
例如,我们可以用设置公开属性的方式实现浅克隆——最简单的做法可以这样写:
{
Name = person.Name,
DateOfBirth = person.DateOfBirth, ...
};
对于这种情况,我们需要使用MemberInit方法:
static Operator()
{
var orig = Expression.Parameter(typeof(T), "orig");
// for each read/write property on T, create a // new binding (for the object initializer) that // copies the original's value into the new object
var setProps = from prop in typeof(T).GetProperties (
BindingFlags.Public | BindingFlags.Instance)
where prop.CanRead && prop.CanWrite
select (MemberBinding) Expression.Bind(
prop, Expression.Property(orig, prop));
var body = Expression.MemberInit( // object initializer
Expression.New(typeof(T)), // ctor
setProps // property assignments
);
shallowClone = Expression.Lambda<Func<T, T>>(
body, orig).Compile();
}
这种做法比标准的反射方式的性能要高的多,而且不需要为每种类型维护一段代码,也不会遗漏一些新增的属性。
类似的方法还可以用于其他一些方面,如在DTO对象之间映射数据,在类型与DataTable对象之间建立关系——或比较两个对象是否相等:
x.DateOfBirth == y.DateOfBirth && ...;
在这里我们需要使用AndAlso来结合每个操作:
static Operator()
{
var x = Expression.Parameter(typeof(T), "x");
var y = Expression.Parameter(typeof(T), "y");
var readableProps =
from prop in typeof(T).GetProperties(
BindingFlags.Public | BindingFlags.Instance)
where prop.CanRead
select prop;
Expression combination = null;
foreach (var prop in readableProps)
{
var thisPropEqual = Expression.Equal(
Expression.Property(x, prop),
Expression.Property(y, prop));
if (combination == null)
{ // first
combination = thisPropEqual;
}
else
{ // combine via &&
combination = Expression.AndAlso(
combination, thisPropEqual);
}
}
if (combination == null)
{ // nothing to test; return true
propertiesEqual = delegate { return true; };
}
else
{
propertiesEqual = Expression.Lambda<Func<T, T, bool>>(
combination, x, y).Compile();
}
}