【问题标题】:CreateDelegate instead of Reflection for SetValueCreateDelegate 而不是 SetValue 的反射
【发布时间】:2012-10-19 06:56:55
【问题描述】:

我尝试实现 Jon Skeet 的 this question 解决方案发布在此 blog post 上,以使用委托的非反射方法替换 SetValue 方法。

blog post 中的解决方案的区别在于SetValuevoid,而我在MethodInfo miConstructedHelper = miGenericHelper.MakeGenericMethod(typeof(G), pMethod.GetParameters()[0].ParameterType, pMethod.ReturnType); 行得到The type 'System.Void' may not be used as a type argument. 异常。

这是我对MagicMethod 的实现:

public class Instantiator<T> where T : new()
{
    private T instance;
    private IDictionary<string, PropertyInfo> properties;

    private Func<PropertyInfo, object, object> _fncSetValue;

    public Instantiator()
    {
        Type type = typeof(T);
        properties = type.GetProperties().GroupBy(p => p.Name).ToDictionary(g => g.Key, g => g.ToList().First());

        MethodInfo miSetValue = typeof(PropertyInfo).GetMethod("SetValue", new Type[] { typeof(object), typeof(object), typeof(object[]) });
        _fncSetValue = SetValueMethod<PropertyInfo>(miSetValue);
    }

    public void CreateNewInstance()
    {
        instance = new T();
    }

    public void SetValue(string pPropertyName, object pValue)
    {
        if (pPropertyName == null) return;
        PropertyInfo property;
        if (!properties.TryGetValue(pPropertyName, out property)) return;
        TypeConverter tc = TypeDescriptor.GetConverter(property.PropertyType);

        //substitute this line
        //property.SetValue(instance, tc.ConvertTo(pValue, property.PropertyType), null);
        //with this line
        _fncSetValue(property, new object[] { instance, tc.ConvertTo(pValue, property.PropertyType), null });
    }

    public T GetInstance()
    {
        return instance;
    }

    private static Func<G, object, object> SetValueMethod<G>(MethodInfo pMethod) where G : class
    {
        MethodInfo miGenericHelper = typeof(Instantiator<T>).GetMethod("SetValueMethodHelper", BindingFlags.Static | BindingFlags.NonPublic);
        MethodInfo miConstructedHelper = miGenericHelper.MakeGenericMethod(typeof(G), pMethod.GetParameters()[0].ParameterType, pMethod.ReturnType);
        object retVal = miConstructedHelper.Invoke(null, new object[] { pMethod });
        return (Func<G, object, object>) retVal;
    }

    private static Func<TTarget, object, object> SetValueMethodHelper<TTarget, TParam, TReturn>(MethodInfo pMethod) where TTarget : class
    {
        Func<TTarget, TParam, TReturn> func = (Func<TTarget, TParam, TReturn>)Delegate.CreateDelegate(typeof(Func<TTarget, TParam, TReturn>), pMethod);
        Func<TTarget, object, object> retVal = (TTarget target, object param) => func(target, (TParam) param);
        return retVal;
    }
}

【问题讨论】:

    标签: c# reflection delegates


    【解决方案1】:

    您在代码中使用了FuncFunc 用于具有返回类型的方法。对于返回void 的方法,您需要使用Action


    您的代码需要如下所示:

    public class Instantiator<T> where T : new()
    {
        private T instance;
        private IDictionary<string, PropertyInfo> properties;
    
        private Action<PropertyInfo, object, object, object> _fncSetValue;
    
        public Instantiator()
        {
            Type type = typeof(T);
            properties = type.GetProperties()
                             .GroupBy(p => p.Name)
                             .ToDictionary(g => g.Key, g => g.ToList().First());
    
            var types = new Type[] { typeof(object), typeof(object),
                                     typeof(object[]) };
            var miSetValue = typeof(PropertyInfo).GetMethod("SetValue", types);
            _fncSetValue = SetValueMethod<PropertyInfo>(miSetValue);
        }
    
        public void CreateNewInstance()
        {
            instance = new T();
        }
    
        public void SetValue(string pPropertyName, object pValue)
        {
            if (pPropertyName == null) return;
            PropertyInfo property;
            if (!properties.TryGetValue(pPropertyName, out property)) return;
            TypeConverter tc = TypeDescriptor.GetConverter(property.PropertyType);
    
            var value = tc.ConvertTo(pValue, property.PropertyType);
            _fncSetValue(property, instance, value, null);
        }
    
        public T GetInstance()
        {
            return instance;
        }
    
        private static Action<G, object, object, object> SetValueMethod<G>(MethodInfo pMethod) where G : class
        {
            var miGenericHelper = 
                typeof(Instantiator<T>).GetMethod("SetValueMethodHelper", 
                                                  BindingFlags.Static | 
                                                  BindingFlags.NonPublic);
    
            var parameters = pMethod.GetParameters();
            var miConstructedHelper = miGenericHelper.MakeGenericMethod(typeof(G), 
                                          parameters[0].ParameterType,
                                          parameters[1].ParameterType,
                                          parameters[2].ParameterType);
    
            var retVal = miConstructedHelper.Invoke(null, new object[] { pMethod });
            return (Action<G, object, object, object>) retVal;
        }
    
        private static Action<TTarget, object, object, object> SetValueMethodHelper<TTarget, TParam1, TParam2, TParam3>(MethodInfo pMethod) where TTarget : class
        {
            var func = (Action<TTarget, TParam1, TParam2, TParam3>)Delegate.CreateDelegate(typeof(Action<TTarget, TParam1, TParam2, TParam3>), pMethod);
            Action<TTarget, object, object, object> retVal =
                (target, param1, param2, param3) => 
                    func(target, (TParam1) param1, (TParam2) param2, (TParam3) param3);
    
            return retVal;
        }
    }
    

    由于您不想调用 Jon Skeet 之类的任意方法,因此您可以大大简化代码。无需在您的代码中调用MethodInfo.Invoke,因此无需委托。您可以直接在返回的PropertyInfo 上直接调用SetValue。无论如何都不需要使用委托的迂回方法,而委托又会准确地调用该方法。此外,不需要进行类型转换,因为 SetValue 无论如何都需要 object
    你的代码可以这么简单:

    public class SimpleInstantiator<T> where T : new()
    {
        private T instance;
        private IDictionary<string, PropertyInfo> properties;
    
        public SimpleInstantiator()
        {
            Type type = typeof(T);
            properties = type.GetProperties()
                             .GroupBy(p => p.Name)
                             .ToDictionary(g => g.Key, g => g.ToList().First());
        }
    
        public void CreateNewInstance()
        {
            instance = new T();
        }
    
        public void SetValue(string pPropertyName, object pValue)
        {
            if (pPropertyName == null) return;
    
            PropertyInfo property;
            if (!properties.TryGetValue(pPropertyName, out property)) return;
    
            property.SetValue(instance, pValue, null);
        }
    
        public T GetInstance()
        {
            return instance;
        }
    }
    

    性能测试表明,这个版本只占用了前一个版本的 50% 左右。
    性能的微小提升是由于我们在调用链中避免了两个不必要的委托。然而,绝大多数速度的提升在于我们移除了类型转换。

    【讨论】:

    • 谢谢,这解决了上述异常。但是现在我得到Error binding to target method.就行了MethodInfo miConstructedHelper = miGenericHelper.MakeGenericMethod(typeof(G), pMethod.GetParameters()[0].ParameterType, pMethod.ReturnType);我想是因为SetValue有不止一个参数...
    • @Mentoliptus:你需要删除pMethod.ReturnType。您需要从SetValueMethodHelper 中删除通用参数TReturn
    • 我做到了,那部分工作正常。 MakeGenericMethod 接受 Type[] 参数,一个目标,另一个作为方法参数。博文中的方法只有一个参数,因此是pMethod.GetParameters()[0].ParameterType,但我有3个参数。
    • 哪里有三个参数?
    • 我添加了, pMethod.GetParameters()[1].ParameterType, pMethod.GetParameters()[2].ParameterType,但没有想到我必须将参数添加到所有Action 声明中。非常非常感谢你!我在这里学到了很多东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多