IndexExpression 到 MemberBinding
IndexExpression to MemberBinding
我有一个问题需要帮助。有一个 object 说 TestObject
public class TestObject
{
public int Value { get; set; }
public string StringValue {get;set;}
}
此 object 正在动态写入 csv 文件,其中 "Value" 和 "StringValue"
header 下面初始化了这种类型的示例列表
var testObjects = new List<TestObject> { new TestObject() { Value = 1, StringValue = "A" },
new TestObject() { Value = 2, StringValue = "B" }, new TestObject() { Value = 3, StringValue = "C" } };
将表示为 table
╔═══════╦═════════════╗
║ Value ║ StringValue ║
╠═══════╬═════════════╣
║ 1 ║ A ║
║ 2 ║ B ║
║ 3 ║ C ║
╚═══════╩═════════════╝
我正在为此 object 动态创建成员绑定列表,在本例中是 2 个绑定; 1 个用于 "Value" 属性,1 个用于 "StringValue" 属性。让我们调用这些绑定的列表,其中 testObjectBindings 是 MemberBindings 的列表。
我最终想重新创建已编写的测试列表object,因此伪代码将是
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 构建测试 object 并像下面那样分配绑定
MemberInitExpression body = Expression.MemberInit(Expression.New(typeof(TestObject)), propertyExpression[0] { });
这不起作用,因为 IndexExpression 不是 MemberBinding。如何将返回的 属性 转换或表示为 MemberBinding 以便我可以使用 MemberInit 调用?
感谢阅读
我也不确定你到底想干什么。下面是使用 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);
}
我假设如下,因为我不太清楚你的问题:
- 您想将 CSV 文件转换为 objects 的列表。
- CSV 文件的第一行包含 header,其中 header 中的名称映射到目标 objects 的 属性 个名称。
- objects是使用默认构造函数创建的,之后你一个一个设置属性(
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" });
调用时生成的代码行与我们在上面的示例中开始构建的代码匹配(除了一些语法差异)。
创建方法后,我们只需向其提供 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");
这个解决方案没有解决的问题我能想到:
- 数据本地化,不同的 date/number 格式。
Convert.ChangeType
接受允许指定文化的 IFormatProvider
。 属性 属性然后可以指定如何将 CSV 数据转换为适当的类型。
- 复杂类型/自定义类型构造。 (例如
Person.Address.Street
)
- 适当的错误处理
为什么 MemberBinding
不是正确的选择
System.Linq.Expressions
命名空间使我们能够从本质上构建代码生成器。 MemberBinding
class 是 MemberAssignment
、MemberListBinding
和 MemberMemberBinding
class 的基础 class。这些表达式中的每一个都代表也可以使用 属性 或字段设置器表达的操作。使用 TestObject
:
的原始示例
var obj = new TestObject {
Value = 1 //MemberBinding expression
};
这相当于:
var obj = new TestObject();
obj.Value = 1;
正如我们所见,编写代码生成器比编写我们生成的代码要复杂得多,我们希望它尽可能简单。使用不同种类的语法通常不会降低它的复杂性。
我有一个问题需要帮助。有一个 object 说 TestObject
public class TestObject
{
public int Value { get; set; }
public string StringValue {get;set;}
}
此 object 正在动态写入 csv 文件,其中 "Value" 和 "StringValue" header 下面初始化了这种类型的示例列表
var testObjects = new List<TestObject> { new TestObject() { Value = 1, StringValue = "A" },
new TestObject() { Value = 2, StringValue = "B" }, new TestObject() { Value = 3, StringValue = "C" } };
将表示为 table
╔═══════╦═════════════╗
║ Value ║ StringValue ║
╠═══════╬═════════════╣
║ 1 ║ A ║
║ 2 ║ B ║
║ 3 ║ C ║
╚═══════╩═════════════╝
我正在为此 object 动态创建成员绑定列表,在本例中是 2 个绑定; 1 个用于 "Value" 属性,1 个用于 "StringValue" 属性。让我们调用这些绑定的列表,其中 testObjectBindings 是 MemberBindings 的列表。
我最终想重新创建已编写的测试列表object,因此伪代码将是
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 构建测试 object 并像下面那样分配绑定
MemberInitExpression body = Expression.MemberInit(Expression.New(typeof(TestObject)), propertyExpression[0] { });
这不起作用,因为 IndexExpression 不是 MemberBinding。如何将返回的 属性 转换或表示为 MemberBinding 以便我可以使用 MemberInit 调用?
感谢阅读
我也不确定你到底想干什么。下面是使用 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);
}
我假设如下,因为我不太清楚你的问题:
- 您想将 CSV 文件转换为 objects 的列表。
- CSV 文件的第一行包含 header,其中 header 中的名称映射到目标 objects 的 属性 个名称。
- objects是使用默认构造函数创建的,之后你一个一个设置属性(
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" });
调用时生成的代码行与我们在上面的示例中开始构建的代码匹配(除了一些语法差异)。
创建方法后,我们只需向其提供 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");
这个解决方案没有解决的问题我能想到:
- 数据本地化,不同的 date/number 格式。
Convert.ChangeType
接受允许指定文化的IFormatProvider
。 属性 属性然后可以指定如何将 CSV 数据转换为适当的类型。 - 复杂类型/自定义类型构造。 (例如
Person.Address.Street
) - 适当的错误处理
为什么 MemberBinding
不是正确的选择
System.Linq.Expressions
命名空间使我们能够从本质上构建代码生成器。 MemberBinding
class 是 MemberAssignment
、MemberListBinding
和 MemberMemberBinding
class 的基础 class。这些表达式中的每一个都代表也可以使用 属性 或字段设置器表达的操作。使用 TestObject
:
var obj = new TestObject {
Value = 1 //MemberBinding expression
};
这相当于:
var obj = new TestObject();
obj.Value = 1;
正如我们所见,编写代码生成器比编写我们生成的代码要复杂得多,我们希望它尽可能简单。使用不同种类的语法通常不会降低它的复杂性。