使用表达式树而不是反射获取和设置对象属性

本文关键字:获取 设置 对象 属性 反射 表达式 | 更新日期: 2023-09-27 18:36:11

我想动态获取和设置对象属性,如下所示:

public class Person
{
    public string Name {get; set; }
}
public class Testing
{
    public void Run()
    {
        var p = new Person();
        SetValue(p, "Name", "Henry");
        var name = GetValue(p, "Name");
    }
}

请问我能得到使用动态方法(或表达式树)创建 GetValue 和 SetValue 方法的帮助吗?

我打算将编译的表达式保存在字典中,以加快将来的获取/设置调用。

使用表达式树而不是反射获取和设置对象属性

你真的想使用表达式树吗? 对于这个简单的场景,我会尝试通过获取IL生成器来使用Reflection.Emit API直接编译成DynamicMethod。但。。对于表达式树,我为您编写了一个帮助程序:

 public class PropertyManager : DynamicObject
    {
        private static Dictionary<Type, Dictionary<string, GetterAndSetter>> _compiledProperties = new Dictionary<Type, Dictionary<string, GetterAndSetter>>();
        private static Object _compiledPropertiesLockObject = new object();
        private class GetterAndSetter
        {
            public Action<object, Object> Setter { get; set; }
            public Func<Object, Object> Getter { get; set; }
        }
        private Object _object;
        private Type _objectType;
        private PropertyManager(Object o)
        {
            _object = o;
            _objectType = o.GetType();
        }
        public static dynamic Wrap(Object o)
        {
            if (o == null)
                return null; // null reference will be thrown
            var type = o.GetType();
            EnsurePropertySettersAndGettersForType(type);
            return new PropertyManager(o) as dynamic;
        }
        private static void EnsurePropertySettersAndGettersForType(Type type)
        {
            if (false == _compiledProperties.ContainsKey(type))
                lock (_compiledPropertiesLockObject)
                    if (false == _compiledProperties.ContainsKey(type))
                    {
                        Dictionary<string, GetterAndSetter> _getterAndSetters;
                        _compiledProperties[type] = _getterAndSetters = new Dictionary<string, GetterAndSetter>();
                        var properties = type.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
                        foreach (var property in properties)
                        {
                            var getterAndSetter = new GetterAndSetter();
                            _getterAndSetters[property.Name] = getterAndSetter;
                            // burn getter and setter
                            if (property.CanRead)
                            {
                                // burn getter
                                var param = Expression.Parameter(typeof(object), "param");
                                Expression propExpression = Expression.Convert(Expression.Property(Expression.Convert(param, type), property), typeof(object));
                                var lambda = Expression.Lambda(propExpression, new[] { param });
                                var compiled = lambda.Compile() as Func<object, object>;
                                getterAndSetter.Getter = compiled;
                            }
                            if (property.CanWrite)
                            {
                                var thisParam = Expression.Parameter(typeof(Object), "this");
                                var theValue = Expression.Parameter(typeof(Object), "value");
                                var isValueType = property.PropertyType.IsClass == false && property.PropertyType.IsInterface == false;
                                Expression valueExpression;
                                if (isValueType)
                                    valueExpression = Expression.Unbox(theValue, property.PropertyType);
                                else
                                    valueExpression = Expression.Convert(theValue, property.PropertyType);
                                var thisExpression = Expression.Property (Expression.Convert(thisParam, type), property);

                                Expression body = Expression.Assign(thisExpression, valueExpression);
                                var block = Expression.Block(new[]
                                {
                                    body,
                                    Expression.Empty ()
                                });
                                var lambda = Expression.Lambda(block, thisParam, theValue);
                                getterAndSetter.Setter = lambda.Compile() as Action<Object, Object>;
                            }
                        }
                    }
        }
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var memberName = binder.Name;
            result = null;
            Dictionary<string, GetterAndSetter> dict;
            GetterAndSetter getterAndSetter;
            if (false == _compiledProperties.TryGetValue(_objectType, out dict)
                || false == dict.TryGetValue(memberName, out getterAndSetter))
            {
                return false;
            }
            if (getterAndSetter.Getter == null)
                throw new NotSupportedException("The property has no getter!");
            result = getterAndSetter.Getter(_object);
            return true;
        }
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            var memberName = binder.Name;
            Dictionary<string, GetterAndSetter> dict;
            GetterAndSetter getterAndSetter;
            if (false == _compiledProperties.TryGetValue(_objectType, out dict)
                || false == dict.TryGetValue(memberName, out getterAndSetter))
            {
                return false;
            }
            if (getterAndSetter.Setter == null)
                throw new NotSupportedException("The property has no getter!");
            getterAndSetter.Setter(_object, value);
            return true;
        }
    }

这就是您可以使用它的方式:

Person p = new Person();
p.Name = "mama";
var wrapped = PropertyManager.Wrap(p);
var val = wrapped.Name; // here we are using our compiled method ...

很明显,您可以提取我的编译逻辑以使用字符串,而不是让 DLR 为您提供属性名称:)