This is a long story): I have several types that look like this:
public class Model { private readonly SomeType _member; private readonly AnotherType _member2; public Model(SomeType member, AnotherType member2) { _member = member; _member2 = member2; } public SomeType Member { get { return _member; } } public AnotherType Member2 { get { return _member2; } } }
I am trying to create several expressions to create an instance of a class, read properties from some other objects (usually ordinary objects) and write values ββto the created private fields of the instance based on the specified naming convention: Prop has the field name: _prop .
I want to say that I want to write the objects below to a new instance of Model :
var anon1 = new { Member = "something" }; // expected: new Model with _member = "something" var anon2 = new { Member2 = "something" }; // expected: new Model with _member2 = "something" var anon3 = new { Member = "something", Member2 = "something else" }; // expected: new Model with _member = "something" and _member2 = "something else"
I created the code below, it creates new instances, but does nothing with fields. Can you help me find where I made a mistake, please?
public class InstanceCreator { static public readonly Func<FieldInfo, PropertyInfo, bool> NamingConvention; static InstanceCreator() { NamingConvention = (f, p) => { var startsWithUnderscope = f.Name.StartsWith("_"); var hasSameName = p.Name.Equals(f.Name.Remove(0, 1), StringComparison.OrdinalIgnoreCase); var hasSameType = f.FieldType == p.PropertyType; return startsWithUnderscope && hasSameName && hasSameType && f.IsInitOnly && !p.CanWrite; }; } private readonly Type _type; private readonly Func<dynamic, dynamic> _creator; public InstanceCreator(Type type) { _type = type; var ctor = GetCtor(type); var propertyToFieldWriters = MakeWriters(type); _creator = MakeCreator(type, ctor, propertyToFieldWriters); } private Expression GetCtor(Type type) { if (type == typeof(string)) // ctor for string return Expression.Lambda<Func<dynamic>>( Expression.Constant(string.Empty)); if (type.IsValueType || // type has a parameterless ctor type.GetConstructor(Type.EmptyTypes) != null) return Expression.Lambda<Func<dynamic>>(Expression.New(type)); var info = typeof(FormatterServices).GetMethod("GetUninitializedObject"); var call = Expression.Call(info, Expression.Constant(type)); return call; //return Expression.Lambda<Func<dynamic>>(call); } private IEnumerable<PropertyToFieldMapper> MakeWriters(Type type) { var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic); var list = (from field in fields let property = properties.FirstOrDefault(prop => NamingConvention(field, prop)) where property != null select new PropertyToFieldMapper(field, property)).ToList(); foreach (var item in list) { var sourceParameter = Expression.Parameter(type, "sourceParameter"); var propertyGetter = Expression.Property(sourceParameter, item.Property.Name); var targetParameter = Expression.Parameter(type, "targetParameter"); var setterInfo = item.Field.GetType().GetMethod("SetValue", new[] { typeof(object), typeof(object) }); var setterCall = Expression.Call(Expression.Constant(item.Field), setterInfo, new Expression[] { Expression.Convert(targetParameter,typeof(object)), Expression.Convert(propertyGetter,typeof(object)) }); var call = Expression.Lambda(setterCall, new[] { targetParameter, sourceParameter }); item.Call = call; } return list; } private Func<dynamic, dynamic> MakeCreator( Type type, Expression ctor, IEnumerable<PropertyToFieldMapper> writers) { var list = new List<Expression>(); // creating new target var targetVariable = Expression.Variable(type, "targetVariable"); list.Add(Expression.Assign(targetVariable, Expression.Convert(ctor, type))); // find all properties in incoming data var sourceParameter = Expression.Parameter(typeof(object), "sourceParameter"); var sourceTypeVariable = Expression.Variable(typeof(Type)); var sourceTypeGetter = Expression.Call(sourceParameter, "GetType", Type.EmptyTypes); list.Add(Expression.Assign(sourceTypeVariable, sourceTypeGetter)); var sourcePropertiesVariable = Expression.Variable(typeof(PropertyInfo[])); var sourcePropertiesGetter = Expression.Call(sourceTypeVariable, "GetProperties", Type.EmptyTypes); list.Add(Expression.Assign(sourcePropertiesVariable, sourcePropertiesGetter)); // itrate over writers and add their Call to block foreach (var writer in writers) { var param = Expression.Parameter(typeof(PropertyInfo)); var prop = Expression.Property(param, "Name"); var eq = Expression.Equal(Expression.Constant(writer.Property.Name), prop); var any = CallAny.Call(sourcePropertiesVariable, Expression.Lambda(eq, param)); var predicate = Expression.IfThen(any, Expression.Lambda(writer.Call, new[] { targetVariable, sourceParameter })); list.Add(predicate); } list.Add(targetVariable); var block = Expression.Block(new[] { targetVariable, sourceTypeVariable, sourcePropertiesVariable }, list); var lambda = Expression.Lambda<Func<dynamic, dynamic>>( block, new[] { sourceParameter } ); return lambda.Compile(); } public dynamic Create(dynamic data) { return _creator.Invoke(data); } private class PropertyToFieldMapper { private readonly FieldInfo _field; private readonly PropertyInfo _property; public PropertyToFieldMapper(FieldInfo field, PropertyInfo property) { _field = field; _property = property; } public FieldInfo Field { get { return _field; } } public PropertyInfo Property { get { return _property; } } public Expression Call { get; set; } } }
In addition, I have a CallAny class created from here .
public class CallAny { public static Expression Call(Expression collection, Expression predicate) { Type cType = GetIEnumerableImpl(collection.Type); collection = Expression.Convert(collection, cType); Type elemType = cType.GetGenericArguments()[0]; Type predType = typeof(Func<,>).MakeGenericType(elemType, typeof(bool)); // Enumerable.Any<T>(IEnumerable<T>, Func<T,bool>) var anyMethod = (MethodInfo) GetGenericMethod(typeof(Enumerable), "Any", new[] { elemType }, new[] { cType, predType }, BindingFlags.Static); return Expression.Call(anyMethod, collection, predicate); } static MethodBase GetGenericMethod(Type type, string name, Type[] typeArgs, Type[] argTypes, BindingFlags flags) { int typeArity = typeArgs.Length; var methods = type.GetMethods() .Where(m => m.Name == name) .Where(m => m.GetGenericArguments().Length == typeArity) .Select(m => m.MakeGenericMethod(typeArgs)); return Type.DefaultBinder.SelectMethod(flags, methods.ToArray(), argTypes, null); } static Type GetIEnumerableImpl(Type type) { // Get IEnumerable implementation. Either type is IEnumerable<T> for some T, // or it implements IEnumerable<T> for some T. We need to find the interface. if (IsIEnumerable(type)) return type; Type[] t = type.FindInterfaces((m, o) => IsIEnumerable(m), null); Debug.Assert(t.Length == 1); return t[0]; } static bool IsIEnumerable(Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>); } }
And here is the use:
var inst = new InstanceCreator(typeof (Model)).Create(new {MyData});