http://blog.zhaojie.me/2010/05/generate-dynamic-method-with-expression-tree-in-dot-net-4.html
使用.NET 4.0中的表达式树生成动态方法
2010-05-12 17:03 by 老赵, 1877 visits为了在模型为dynamic类型的视图中使用一个匿名对象,我们在上一篇文章里为匿名对象创建了对应的动态类型。于是在使用时,我们会创建动态类型的对象,然后将匿名对象的属性赋值给动态对象的公开字段上。在赋值时我们使用了反射,再加上这个方法使用比较频繁,因此使用更好的方法来优化性能便是个很自然的选择。在.NET 1.0中,我们需要Emit;在.NET 2.0中则增加了DynamicMethod,相对简化了单个动态方法的创建过程;在.NET 3.5中则增加了可编译表达式树,可谓前进了一大步——那么在.NET 4.0中呢?
使用.NET 3.5中的表达式树,我们可以动态创建出一个表达式结构,然后编译成一个委托对象。这种做法我使用过很多次(比如这里或这里),它的优势在于我们可以将关注点放在“表达式”层面上,而不用直接使用繁琐易错的Emit操作。更关键的是,此外,表达式树还会帮我们隐藏了自动类型转换、或是装箱/拆箱等原本需要开发人员直接关注的问题。例如,在表达式层面上,整数相加和字符串的连接都是个Add操作,但是在IL级别它们一个是Add指令,另一个却是String.Concat方法的调用。
不过.NET 3.5中的表达式树有个限制,那便它只能创建出“表达式(Expression)”,而无法创建“语句(Statement)”。例如,我们无法定义一个包含循环的方法。不过这一切在.NET 4.0里得到了改善。.NET 4.0引入了一部分原本在DLR中的更丰富的表达式树,包含变量声明、for循环、判断、GOTO跳转等各种表达式,因此我们基本上可以用它来表达任意一段完整的逻辑了。那么,它又该如何来改进上一篇文章里的性能问题呢?
上文末尾我提到,我们可以把动态类型创建成如下模样:
public class DynamicType { public string Abc; public int Ijk; public bool Xyz; ... public DynamicType(object entity) { var strongTyped = (EntityType)entity; this.Abc = strongTyped.Abc; this.Ijk = strongTyped.Ijk; this.Xyz = strongTyped.Xyz; ... } }
不过事实上,我们完全可以还是创建一个最为普通的动态类型:
public class DynamicType { public string Abc; public int Ijk; public bool Xyz; ... }
只要再配合这么一个动态生成的方法就行了:
entity => { var dynamicEntity = new DynamicType(); var strongTyped = (EntityType)entity; dynamicEntity.Abc = strongTyped.Abc; dynamicEntity.Ijk = strongTyped.Ijk; dynamicEntity.Xyz = strongTyped.Xyz; ... return (object)dynamicEntity; }
是不是很容易?而且事实上生成这么一个方法也非常简单,和以前一样,只管创建表达式树即可:
private static Func<object, object> DynamicFactoryCreator(Type entityType) { var dynamicType = CreateDynamicType(entityType); // define the parameter var parameterExpr = Expression.Parameter(typeof(object), "entity"); // collect the body var bodyExprs = new List<Expression>(); // code: var dynamicEntity = new DynamicType(); var dynamicEntityExpr = Expression.Variable(dynamicType, "dynamicEntity"); var newDynamicTypeExpr = Expression.New(dynamicType); var assignDynamicEntityExpr = Expression.Assign(dynamicEntityExpr, newDynamicTypeExpr); bodyExprs.Add(assignDynamicEntityExpr); // code: var strongTyped = (EntityType)entity; var strongTypedExpr = Expression.Variable(entityType, "strongTyped"); var castEntityExpr = Expression.Convert(parameterExpr, entityType); var assignStrongTypedExpr = Expression.Assign(strongTypedExpr, castEntityExpr); bodyExprs.Add(assignStrongTypedExpr); // generate code for fields' assignments foreach (var property in entityType.GetProperties()) { // code: dynamicEntity.Xyz = strongTyped.Xyz var fieldExpr = Expression.Field(dynamicEntityExpr, property.Name); var propertyExpr = Expression.Property(strongTypedExpr, property); var assignFieldExpr = Expression.Assign(fieldExpr, propertyExpr); bodyExprs.Add(assignFieldExpr); } // code: return (object)dynamicEntity; var castResultExpr = Expression.Convert(dynamicEntityExpr, typeof(object)); bodyExprs.Add(castResultExpr); // code: { ... } var methodBodyExpr = Expression.Block( typeof(object), /* return type */ new[] { dynamicEntityExpr, strongTypedExpr } /* local variables */, bodyExprs /* body expressions */); // code: entity => { ... } var lambdaExpr = Expression.Lambda<Func<object, object>>(methodBodyExpr, parameterExpr); return lambdaExpr.Compile(); }
上面这段代码创建的完全是我们准备好的那种动态方法形式,这点从注释中也可以看出。首先,我们会使用上文定义的CreateDynamicType方法中创建一个动态类型,然后使用一个List
虽说这段代码似乎比较长,但我个人认为还是相当清晰的。我建议您可以适当把玩一下.NET 4.0中的表达式树类库,例如创建一些包含for循环,goto跳转等逻辑的方法体。根据我的经验,其实需要动态生成方法的场景似乎也真比原本我想象中要来的多。
最后,再改写原来的DynamicFactory类及ToDynamic扩展方法:
public static class DynamicFactory { private static ConcurrentDictionary<Type, Func<object, object>> s_dynamicEntityFactories = new ConcurrentDictionary<Type, Func<object, object>>(); private static Func<Type, Func<object, object>> s_factoryCreator = new Func<Type, Func<object, object>>(DynamicFactoryCreator); public static object ToDynamic(this object entity) { var entityType = entity.GetType(); var factory = s_dynamicEntityFactories.GetOrAdd(entityType, s_factoryCreator); return factory(entity); } ... }
好,我们的性能优化到这里就完成了。不过再回到我们原本的问题——如果我们要在ASP.NET MVC的视图中使用dynamic类型作为参数,并传递匿名对象的话,那么还能使用什么办法来避免古怪的“无法找到成员”异常呢?或者这么说,如果不像现在这样生成动态类型,那么ToDynamic方法还可以怎么实现呢?