技术开发 频道

表达式即编译器

  Expression的妙用

  到目前为止,我们只是观察了一个用C#就能完成的简单示例——这自然没什么大不了的。所以我们现在来看一些C#无法做到的东西……我经常被人问及,有什么 方法可以编写一个通用的“加法”操作,可以对任意类型进行计算。简而言之:没有这样的语法。不过我们可以使用Expression来做到这一点。

  简单起见,我使用var关键字来代替显式变量类型,我们现在将要观察如何简单地缓存可重用的委托对象:

public static class Operator<T>
{
    
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”方法隐藏了许多复杂性:值类型或引用类型、基础操作(直接有IL指令与之对应)、隐 藏操作符(方法为类型定义的一部分)以及提升操作(用于自动处理Nullable)等等。

  这个做法甚至支持用户自定义类型——只要你为它定义了+运算符。如果没有合适的运算符,这段代码便会抛出异常。在实际使用过程中这不太会是个问题,如果你不放心的话,可以在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

  例如,我们可以用设置公开属性的方式实现浅克隆——最简单的做法可以这样写:

person => new Person

{  

    Name
= person.Name,  

    DateOfBirth
= person.DateOfBirth, ...  

};

  对于这种情况,我们需要使用MemberInit方法:

private static readonly Func<T, T> shallowClone;

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,y) => x.Name == y.Name &&

   x.DateOfBirth
== y.DateOfBirth && ...;

  在这里我们需要使用AndAlso来结合每个操作:

private static readonly Func<T, T, bool> propertiesEqual;
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();
   }

}
0
相关文章