zoukankan      html  css  js  c++  java
  • zt:使用.NET 4.0中的表达式树生成动态方法

    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容器收集方法体内所有的语句,再把它们交给Expression.Block方法创建一个“方法块”(而不仅仅是“表达式”),最后生成Lambda表达式,编译完成。

    虽说这段代码似乎比较长,但我个人认为还是相当清晰的。我建议您可以适当把玩一下.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方法还可以怎么实现呢?

  • 相关阅读:
    html转义
    mongodb 数据库 基础使用
    xpath基本语法
    HTTP
    JavaScript笔记6-数组新方法
    JavaScript笔记5-事件
    JavaScript笔记3--标识符和保留字
    JavaScript笔记4-数组
    jquery笔记1--选择器
    JavaScript笔记2
  • 原文地址:https://www.cnblogs.com/youfan/p/2093822.html
Copyright © 2011-2022 走看看