在.NET 4.5中动态生成代码的最简单方法是什么?

时间:2013-07-30 14:02:53

标签: .net code-generation

我正在编写一种特定的对象映射器。基本上我想将具有字段DataTableab的{​​{1}}转换为具有属性ca的对象和b(对象的类将手写)。将有许多不同的DataTable和许多不同的类需要映射到,所以我想制作一个执行此数据复制的通用机制。基本上,我想要以下功能:

c

现在,我可以用Reflection做到这一点,但这很慢。该功能将成为框架的核心,并将经常使用。所以我在考虑动态代码生成。第一次在特定的public T Map<T>(DataTable t) where T: new() { ... } 上运行此方法时,它将执行必要的反射并发出一个匿名方法,该方法执行所有适当的映射。下次它将运行该代码。这应尽可能高效。

只有一个问题 - 我从未在运行时发出代码。我怎么做?我看了T,但他们只能表达,而不是一系列陈述。

然后是CodeDOM和Expressions。那种工作 - 我可以生成C#代码作为字符串,在运行中编译它然后获得引用。但它涉及C#编译器并生成一个全新的内存中程序集。对于一个简单的方法,听起来有点......重量级。

有一些更简单的方法吗?生成轻量级匿名方法的东西没有附加到任何程序集(或附加到现有程序集)?


好的,因为人们要求一个例子。

这是一个手写的课程

CSharpCodeProvider

这是一个手工准备的DataTable(在现实生活中,它将来自外部库):

class MyBusinessObject
{
    public int a;
    public string b { get; set; }
}

以下是应该即时生成的方法:

DataTable t = new DataTable();
t.AddColumn("a", typeof(int));
t.AddColumn("b", typeof(string));
t.AddRow(42, "Meaning");

为了简洁,我省略了其他一些我需要的东西,但这是问题的关键。

4 个答案:

答案 0 :(得分:7)

在.NET 3.5+中动态生成代码的最简单方法是通过LambdaExpression类的LINQ Expression Trees方法将Compile转换为可执行代码。 .NET 4.0极大地扩展了可能性,增加了对.NET 3.5简单表达式之外的代码结构的支持,使您可以构建功能完备的方法。结果代码为您提供与常规编译代码相同的高性能,假设您的表达式生成器在生成代码时应用了C#编译器所需的相同类型的优化。

以下是从代码段生成代码的方法:

// nameToProperty is a dictionary with keys representing string parameters
// that you pass to drow's indexer, and values representing names of properties
// or fields of the target object.
private static Action<DataRow,T> MakeGetter<T>(IDictionary<string,string> nameToProperty) {
    var sequence = new List<Expression>();
    var drowParam = Expression.Parameter(typeof(DataRow));
    var oParam = Expression.Parameter(typeof(T));
    var indexer = typeof(DataRow)
        .GetDefaultMembers()
        .OfType<PropertyInfo>()
        .Where(pinf => pinf.GetIndexParameters().Length == 1
               &&      pinf.GetIndexParameters()[0].ParameterType == typeof(string))
        .Single();
    foreach (var pair in nameToProperty) {
        var indexExpr = Expression.Property(
            drowParam
        ,   indexer
        ,   Expression.Constant(pair.Key));
        sequence.Add(Expression.Assign(
            Expression.PropertyOrField(pair.Value)
        ,   indexExpr
        ));
    }
    return (Action<DataRow,T>)Expression.Lambda(
        Expression.Block(sequence)
    ,   drowParam
    ,   oParam
    ).Compile();
}

使用此方法,您应该能够生成可根据需要执行分配的已编译Action

答案 1 :(得分:2)

我不会那么快地解雇表达。您可以使用几种不同方式之一来表达目标。

  1. 如果您使用的是.NET 4+,则表达式树已经扩展为支持代码块。虽然您不能将此功能与lambda语法糖一起使用,但您可以使用Expression.Block方法创建代码块。

  2. 使用对您要映射的每个字段都有参数的构造函数。生成的代码可以模仿return new T(ExtractA(t), ExtractB(t), ...)。在这种情况下,您将从where T : new()中删除Map<T>约束,而是依赖于具有构造函数的对象模型类,该构造函数可以使用带有每个映射属性的参数的反射找到。

  3. 使用辅助方法执行一系列语句,就像它是单个语句一样。生成的代码可以模仿return ApplyProperties(t, new T(), new[] { applyA, applyB, ... }),其中applyAapplyBAction<DataTable, T>个委托,它们是从设计用于设置单个特定属性的表达式中单独编译的。 ApplyProperties方法是代码中的辅助方法,如下所示:

     private T ApplyProperties<T>(DataTable t, T result, Action<DataTable, T>[] setters)
     {
         foreach (var action in setters)
         {
             action(t, result);
         }
    
         return result;
     }
    

答案 2 :(得分:2)

有时第三方库是最简单的方法,AutoMapper只需几行代码即可完成您想要的工作

//This just needs to be run once, maybe in a static constructor somewhere.
Mapper.CreateMap<IDataReader, MyBusinessObject>();



//This line does your mapping.
List<MyBusinessObject> myBusinessObject = 
    Mapper.Map<IDataReader, List<MyBusinessObject>>(myDataTable.CreateDataReader());

如果您的源数据与您的业务对象不完全匹配,您需要做的就是将一些设置信息添加到CreateMap

class MyBusinessObject
{
    public int Answer;
    public string Question { get; set; }
}

//In some static constructor somewhere, this maps "a" to "Answer" and "b" to "Question".
Mapper.CreateMap<IDataReader, MyBusinessObject>()
      .ForMember(dto => dto.Answer, opt => opt.MapFrom(rdr => rdr["a"]))
      .ForMember(dto => dto.Question, opt => opt.MapFrom(rdr => rdr["b"]));

答案 3 :(得分:2)

晚会,但Marc Gravell有一个很好的实用工具FastMember。使用FastMember,您可以从DataTable映射到对象,甚至是动态。

var accessor = TypeAccessor.Create(type);
string propName = // something known only at runtime

while( /* some loop of data */ ) {
   accessor[obj, propName] = rowValue;
}

我在制作中使用了它,并且表现很好。