又番的自留地

偷菜不如偷师

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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方法还可以怎么实现呢?

posted on 2011-06-29 22:23  Kyle.cj  阅读(820)  评论(2编辑  收藏  举报