您的位置:首页 > 其它

zt:使用.NET 4.0中的表达式树生成动态方法

2011-06-29 22:23 302 查看
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方法还可以怎么实现呢?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: