IndexExpression到MemberBinding

时间:2015-02-16 18:57:40

标签: c# lambda linq-expressions

我有一个问题,我需要一些帮助。有一个对象说TestObject

public class TestObject
{
      public int Value { get; set; }
      public string StringValue {get;set;}
} 

使用" Value"动态地将此对象写入csv文件。 &安培; "的StringValue"存在 标题,以便在

下初始化此类型的样本列表
    var testObjects = new List<TestObject> { new TestObject() { Value = 1, StringValue = "A" },
        new TestObject() { Value = 2, StringValue = "B" }, new TestObject() { Value = 3, StringValue = "C" } };

将表示为表格

╔═══════╦═════════════╗
║ Value ║ StringValue ║
╠═══════╬═════════════╣
║     1 ║ A           ║
║     2 ║ B           ║
║     3 ║ C           ║
╚═══════╩═════════════╝    

我正在为这个对象动态创建一个成员绑定列表,在这个例子中是2个绑定; 1为&#34;价值&#34;属性和1为&#34; StringValue&#34;属性。让我们调用这些绑定的列表,其中testObjectBindings是MemberBindings的列表。

我最终想要重新创建已编写的测试对象列表,以便伪代码

var recreatedList = new List<TestObject>();
for (int i=0; i <3; i++)
{
   //create a new test object and use
   //the binding information
   var newObject = new TestObject()
   {binding[0], binding[1]};
}

根据我对表达的理解,它的工作方式如下

    var indexer = testObjectBindings.GetType()
        .GetDefaultMembers()
        .OfType<PropertyInfo>()
        .First();

//其中0是生成的绑定的索引0处的成员绑定

IndexExpression propertyExpression = Expression.Property(Expression.Constant(testObjectBindings), indexer, 0);

我想使用Expression.MemberInit来构造测试对象并分配如下所示的绑定

 MemberInitExpression body =  Expression.MemberInit(Expression.New(typeof(TestObject)), propertyExpression[0] { });

由于IndexExpression不是MemberBinding,因此不起作用。如何将返回的属性转换或表示为MemberBinding,以便我可以使用MemberInit调用?

感谢您阅读

2 个答案:

答案 0 :(得分:2)

我假设以下内容,因为你的问题我不清楚:

  • 您想将CSV文件转换为对象列表。
  • CSV文件的第一行包含标题,标题中的名称映射到目标对象的属性名称。
  • 使用默认构造函数创建对象,然后逐个设置属性(MemberInit表达式类型表示C#语法糖,在动态构造代码时几乎没有附加值。)

我在最后添加了更多详细信息,以解释为什么MemberBinding在这种情况下没有帮助。

如果是这种情况,我们可以将我们的主要问题归结为动态创建以下方法:

TestObject FromCsvLine(string[] csvFields) {
    var result = new TestObject();
    result.Value = (int)Convert.ChangeType(csvFields[0], typeof(int));
    result.StringValue = (string)Convert.ChangeType(csvFields[1], typeof(string));
    return result;
}

注意Convert.ChangeType调用,无论属性类型如何,其结构都保持不变,我们只需要提供不同的参数,这使得动态构造变得容易。另请注意,可以使用Func<string[], TestObject>来描述此功能的签名。

我们创建此方法所需的数据是:

  • 要反序列化为
  • 的目标类型
  • 列名列表

因此,我们得到以下方法签名:

Func<string[], T> CreateCsvDeserializer<T>(string[] columnNames)
      where T : new()

new()约束在编译时强制类型T将具有带0参数的构造函数。

实施:

private static Func<string[], T> CreateCsvDeserializer<T>(string[] columnNames)
    where T : new()
{
    var resultVariable = Expression.Variable(typeof (T), "result");
    var csvFieldsParameter = Expression.Parameter(typeof (string[]), "csvFields");
    var constructorCall = Expression.Assign(resultVariable, Expression.New(typeof (T)));

    //will contain all code lines that implement the method
    var codeLines = new List<Expression> {constructorCall};

    for (int i = 0; i < columnNames.Length; i++)
    {
        string columnName = columnNames[i];
        PropertyInfo property = typeof (T).GetProperty(columnName);
        if (property == null || !property.CanWrite || !property.GetSetMethod().IsPublic)
        {
            //cannot write to property
            throw new Exception();
        }

        //Convert.ChangeType(object, Type)
        var convertChangeTypeMethod = typeof (Convert).GetMethod("ChangeType",
            new[] {typeof (object), typeof (Type)});

        //csvFields[i]
        var getColumn = Expression.ArrayIndex(csvFieldsParameter, Expression.Constant(i));

        //Convert.ChangeType(csvFields[i], [propertyType])
        var conversion = Expression.Call(convertChangeTypeMethod, getColumn,
            Expression.Constant(property.PropertyType));

        //([propertyType])Convert.ChangeType(csvFields[i], [propertyType])
        var cast = Expression.Convert(conversion, property.PropertyType);

        //result.[property]
        var propertyExpression = Expression.Property(resultVariable, property);

        //result.[property] = ([propertyType])Convert.ChangeType(csvFields[i], [propertyType])
        codeLines.Add(Expression.Assign(propertyExpression, cast));
    }

    //create a line that returns the resultVariable
    codeLines.Add(resultVariable);

    //now, we have a list of code lines, it's time to build our function
    Type returnType = typeof (T);
    var variablesUsed = new[] {resultVariable};
    var codeBlock = Expression.Block(returnType, variablesUsed, codeLines);
    var parameterList = new[] {csvFieldsParameter};
    return Expression.Lambda<Func<string[], T>>(codeBlock, parameterList).Compile();
} 

从调试器中可以看出,使用CreateCsvDeserializer<TestObject>(new [] { "Value", "StringValue" });调用时生成的代码行(除了一些语法差异)与我们在上面的示例中构建的代码相匹配。

Debugger output

创建方法后,我们只需要输入CSV行并构建列表,这相对容易:

private static List<T> BuildFromCsvFile<T>(string path, string separator = ",")
    where T : new()
{
    string[] separators = {separator};
    var lines = File.ReadAllLines(path);
    var deserializer = CreateCsvDeserializer<T>(lines[0].Split(separators, StringSplitOptions.RemoveEmptyEntries));
    return
        lines.Skip(1)
            .Select(s => s.Split(separators, StringSplitOptions.RemoveEmptyEntries))
            .Select(deserializer)
            .ToList();
} 

然后通过调用构建列表,例如BuildFromCsvFile<TestObject>("Data.csv");

这个解决方案没有解决的问题我可以想到我的头脑:

  • 数据的本地化,不同的日期/数字格式。 Convert.ChangeType接受允许指定文化的IFormatProvider。然后,属性属性可以指定如何将CSV数据转换为适当的类型。
  • 复杂类型/自定义类型构造。 (例如Person.Address.Street
  • 体面的错误处理

为什么MemberBinding无法前往

System.Linq.Expressions命名空间允许我们构建代码生成器。 MemberBinding类是MemberAssignmentMemberListBindingMemberMemberBinding类的基类。这些表达式中的每一个都表示也可以使用属性或字段设置器表达的操作。使用TestObject的原始示例:

var obj = new TestObject { 
       Value = 1 //MemberBinding expression
};

这相当于:

var obj = new TestObject();
obj.Value = 1;

正如我们所看到的,编写代码生成器比编写我们生成的代码要复杂得多,我们希望尽可能简单。使用不同类型的语法通常不会使它变得不那么复杂。

答案 1 :(得分:0)

我也不确定你的枪支是什么。以下是使用MemberBindings创建表达式列表的示例代码

        var testObjects = new List<TestObject> 
        { 
            new TestObject { Value = 1, StringValue = "A" },
            new TestObject { Value = 2, StringValue = "B" }, 
            new TestObject { Value = 3, StringValue = "C" } 
        };

        var valueMemberInfo = typeof (TestObject).GetMember("Value").First();
        var stringValueMemberInfo = typeof (TestObject).GetMember("StringValue").First();

        var expressions = new List<Expression>();
        foreach (var to in testObjects)
        {
            var expr = Expression.MemberInit(
                Expression.New(typeof(TestObject)),
                Expression.Bind(valueMemberInfo, Expression.Constant(to.Value)),
                Expression.Bind(stringValueMemberInfo, Expression.Constant(to.StringValue))
            );
            expressions.Add(expr);
        }