从属性读取表达式,并写入专用只读字段 [英] Expression for read from property and write to private readonly field

查看:144
本文介绍了从属性读取表达式,并写入专用只读字段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

It is a long story ): I have some types 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; } }
}



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

我的意思是我想把下面的对象写入 Model 的新实例:

I mean I want to write below objects 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 have created below code, it creates new instances, but do nothing with fields. Can you help me to find where I did wrong 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; }
    }
}

> CallAny 课程,从此处创建。

Also, I have this 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<>);
    }
}

这里是用法:

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


推荐答案

我应该使用 ExpandoObject 而不是动态关键字。并将它传递给逻辑 IDictionary< string,object> 。这里是解决方案:

Well, I found the problem. I should use ExpandoObject instead of dynamic keyword. And pass it to logic as IDictionary<string, object>. Here is the solution:

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

这篇关于从属性读取表达式,并写入专用只读字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆