从属性读取的表达式和写入私有只读字段

时间:2013-05-21 20:30:11

标签: c# c#-4.0 reflection lambda expression

这是一个很长的故事):我有一些类型看起来像这样:

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; } }
}

我正在尝试创建一些表达式来创建类的实例,从其他一些对象(通常是anon对象)读取属性,并将值写入创建的实例的私有字段 - 基于显示的命名约定:支持具有字段名称:_prop

我的意思是我想将下面的对象写入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"

我创建了以下代码,它创建了新实例,但对字段不执行任何操作。你能帮我找一下我做错的地方吗?

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; }
    }
}

另外,我有CallAny这个类,是从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<>);
    }
}

这是用法:

var inst = new InstanceCreator(typeof (Model)).Create(new {MyData});

1 个答案:

答案 0 :(得分:0)

好吧,我发现了问题。我应该使用ExpandoObject代替dynamic关键字。并将其作为IDictionary<string, object>传递给逻辑。这是解决方案:

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<IDictionary<string, object>, dynamic> _creator;

    public InstanceCreator(Type type) {
        _type = type;
        var ctor = GetCtor(type);
        var propertyToFieldMappers = MakeMappers(type);
        _creator = MakeCreator(type, ctor, propertyToFieldMappers);
    }

    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");
        return Expression.Call(info, Expression.Constant(type));
    }

    private IEnumerable<PropertyToFieldMapper> MakeMappers(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);
        return list;
    }

    private Func<IDictionary<string, object>, dynamic> MakeCreator(
        Type type, Expression ctor,
        IEnumerable<PropertyToFieldMapper> maps) {

        var list = new List<Expression>();
        var vList = new List<ParameterExpression>();

        // creating new target
        var targetVariable = Expression.Variable(type, "targetVariable");
        vList.Add(targetVariable);
        list.Add(Expression.Assign(targetVariable, Expression.Convert(ctor, type)));

        // accessing source
        var sourceType = typeof(IDictionary<string, object>);
        var sourceParameter = Expression.Parameter(sourceType, "sourceParameter");

        // calling source ContainsKey(string) method
        var containsKeyMethodInfo = sourceType.GetMethod("ContainsKey", new[] { typeof(string) });

        var accessSourceIndexerProp = sourceType.GetProperty("Item");
        var accessSourceIndexerInfo = accessSourceIndexerProp.GetGetMethod();

        // itrate over writers and add their Call to block
        var containsKeyMethodArgument = Expression.Variable(typeof(string), "containsKeyMethodArgument");
        vList.Add(containsKeyMethodArgument);
        foreach (var map in maps) {
            list.Add(Expression.Assign(containsKeyMethodArgument, Expression.Constant(map.Property.Name)));
            var containsKeyMethodCall = Expression.Call(sourceParameter, containsKeyMethodInfo,
                                                        new Expression[] { containsKeyMethodArgument });

            // creating writer
            var sourceValue = Expression.Call(sourceParameter, accessSourceIndexerInfo,
                                              new Expression[] { containsKeyMethodArgument });
            var setterInfo = map.Field.GetType().GetMethod("SetValue", new[] { typeof(object), typeof(object) });
            var setterCall = Expression.Call(Expression.Constant(map.Field), setterInfo,
                new Expression[] {
                                     Expression.Convert(targetVariable,typeof(object)),
                                     Expression.Convert(sourceValue,typeof(object))
                                 });
            list.Add(Expression.IfThen(containsKeyMethodCall, setterCall));
        }
        list.Add(targetVariable);

        var block = Expression.Block(vList, list);

        var lambda = Expression.Lambda<Func<IDictionary<string, object>, dynamic>>(
            block, new[] { sourceParameter }
            );

        return lambda.Compile();
    }

    public dynamic Create(IDictionary<string, object> 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; }
        }
    }
}