What's the easiest way to generate code dynamically in .NET 4.5?

前端 未结 4 1106
旧时难觅i
旧时难觅i 2020-12-31 23:41

I\'m writing a specific kind of object-mapper. Basically I want to convert from a DataTable that has the fields a, b and c

相关标签:
4条回答
  • 2021-01-01 00:11

    The easiest way to generate code dynamically in .NET 3.5+ is by converting LINQ Expression Trees to executable code through the Compile method of the LambdaExpression class. .NET 4.0 has greatly expanded the possibilities, adding support for code structures beyond simple expressions of .NET 3.5, letting you construct fully functional methods. The resultant code gives you the same high performance as regularly compiled code would, assuming that your expression generator has applied the same kinds of optimizations a C# compiler would while generating the code.

    Here is how you could generate the code from your snippet:

    // 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();
    }
    

    With this method in place, you should be able to generate compiled Actions that would do the assignments as needed.

    0 讨论(0)
  • 2021-01-01 00:17

    I wouldn't be so quick to dismiss expressions. You can use expressions in one of several different ways to accomplish your goal.

    1. If you are using .NET 4+, you the expression trees have been extended to support blocks of code. While you can't use this functionality with the lambda syntactic sugar, you can use the Expression.Block method to create a code block.

    2. Use a constructor that has a parameter for each field you are mapping. The generated code could mimic return new T(ExtractA(t), ExtractB(t), ...). In this case you would remove the where T : new() constraint from Map<T> and instead rely on your object model classes having a constructor that can be found using reflection with a parameter for each mapped property.

    3. Use helper methods to execute a series of statements as if it were a single statement. The generated code could mimic return ApplyProperties(t, new T(), new[] { applyA, applyB, ... }), where applyA and applyB are Action<DataTable, T> delegates that were separately compiled from expressions designed to set a single specific property. The ApplyProperties method is a helper method in your code like the following:

       private T ApplyProperties<T>(DataTable t, T result, Action<DataTable, T>[] setters)
       {
           foreach (var action in setters)
           {
               action(t, result);
           }
      
           return result;
       }
      
    0 讨论(0)
  • 2021-01-01 00:23

    Sometimes a 3rd party library is the easiest way to do it, AutoMapper will do exactly what you want with just a few lines of code

    //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());
    

    If your source data does not exactly match your business object all you need to do is add some setup info to 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"]));
    
    0 讨论(0)
  • 2021-01-01 00:31

    Late to the party, but Marc Gravell has a nice utility called FastMember. With FastMember, you can map from a DataTable to an object, even a dynamic.

    var accessor = TypeAccessor.Create(type);
    string propName = // something known only at runtime
    
    while( /* some loop of data */ ) {
       accessor[obj, propName] = rowValue;
    }
    

    I'ved used this in production and it performs nicely.

    0 讨论(0)
提交回复
热议问题