I'm assuming the following because your question is quite unclear to me:
- You want to convert a CSV file into a list of objects.
- The first line of a CSV file contains a header, where the names in the header map to a property names for a target objects.
- The objects are created using a default constructor, after which you set the properties one by one (the
MemberInit
expression type represents C# syntax sugar, which has very little added value when dynamically constructing code).
I've added more details at the end to explain why MemberBinding
does not help in this situation.
If this is the case, we can boil our main problem down to creating the following method dynamically:
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;
}
Note the Convert.ChangeType
call, its structure remains the same regardless of property types, we only have to supply a different parameter, making it an easy case to construct dynamically. Also note that the signature of this function can be described using Func<string[], TestObject>
.
The data we need to create this method is:
- The target type to deserialize to
- A list of column names
Therefore, we get the following method signature:
Func<string[], T> CreateCsvDeserializer<T>(string[] columnNames)
where T : new()
The new()
constraint enforces at compile time that the type T
will have a constructor with 0 parameters.
The implementation:
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();
}
As can be seen in the debugger, the generated code lines when called using CreateCsvDeserializer<TestObject>(new [] { "Value", "StringValue" });
matches (apart from some syntax differences) the code we set out to build in the sample above.

After the method has been created, we just need to feed it the CSV lines and build the list, which is relatively easy:
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();
}
And then construct the list by calling, for example BuildFromCsvFile<TestObject>("Data.csv");
Problems not addressed by this solution I can think of off the top of my head:
- Localization of data, different date/number formats. The
Convert.ChangeType
accepts an IFormatProvider
allowing culture to be specified. Property attributes could then specify how to convert the CSV data to the appropriate type.
- Complex types / custom type construction. (e.g.
Person.Address.Street
)
- Decent error handling
Why MemberBinding
is not the way to go
The System.Linq.Expressions
namespace allows us to essentialy build code generators. The MemberBinding
class is a base class for the MemberAssignment
, MemberListBinding
and MemberMemberBinding
classes. Each of these expressions represent operations that could also be expressed using property or field setters. Using your original example of TestObject
:
var obj = new TestObject {
Value = 1 //MemberBinding expression
};
This is equivalent to:
var obj = new TestObject();
obj.Value = 1;
As we have seen, writing a code generator is much more complex than writing the code we generate, and we want to keep it as simple as possible. Using different kinds of syntax usually will not make it less complex.