为属性setter或getter创建一个高性能的开放委托

Dav*_*eis 15 c# reflection delegates

open delegate是没有目标的实例方法的委托.要调用它,您需要提供目标作为其第一个参数.它们是优化代码的一种聪明方法,否则会使用反射并且性能较差.有关开放代表的介绍,请参阅此内容.你在实践中使用它的方法是使用昂贵的反射代码来构建这些开放的委托,但是你可以像一个简单的委托调用一样非常便宜地调用它们.

我正在尝试编写将任意PropertyInfo转换为其setter的委托的代码.到目前为止,我想出了这个:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Test
{
    class TestClass
    {
        static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
        {
            MethodInfo setMethod = property.GetSetMethod();
            if (setMethod != null && setMethod.GetParameters().Length == 1) //skips over nasty index properties
            {
                //To be able to bind to the delegate we have to create a delegate 
                //type like: Action<T,actualType> rather than Action<T,object>.
                //We use reflection to do that
                Type setterGenericType = typeof(Action<,>);
                Type delegateType = setterGenericType.MakeGenericType(new Type[] { typeof(T), property.PropertyType });
                var untypedDelegate = Delegate.CreateDelegate(delegateType, setMethod);

                //we wrap the Action<T,actualType> delegate into an Action<T,object>
                Action<T, object> setter = (instance, value) =>
                {
                    untypedDelegate.DynamicInvoke(new object[] { instance, value });
                };
                return setter;
            }
            else
            {
                return null;
            }
        }

        int TestProp 
        {
            set
            {
                System.Diagnostics.Debug.WriteLine("Called set_TestProp");
            }
        }

        static void Test() 
        {
            PropertyInfo property = typeof(TestClass).GetProperty("TestProp");
            Action<TestClass, object> setter = MakeSetterDelegate<TestClass>(property);
            TestClass instance = new TestClass();
            setter(instance, 5);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

将为getter编写类似的代码.它可以工作,但是setter委托使用DynamicInvoke从Action <derivedType> 转换为Action <object>,我怀疑它正在吃掉我所追求的优化的一部分.所以问题是:

  1. DynamicInvoke真的是一个真正的问题吗?
  2. 不管怎么说呢?

Mar*_*ell 18

DynamicInvoke不会成为一个高性能的二传手.反对泛型内部类型的反射在这里是更好的选择,因为这将允许您使用类型化的委托.另一个选择是DynamicMethod,但是你需要担心一些IL细节.

可能希望查看HyperDescriptor,将IL工作包装到PropertyDescriptor实现中.另一个选项是ExpressionAPI(如果您使用的是.NET 3.5或更高版本):

static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
{
    MethodInfo setMethod = property.GetSetMethod();
    if (setMethod != null && setMethod.GetParameters().Length == 1)
    {
        var target = Expression.Parameter(typeof(T));
        var value = Expression.Parameter(typeof(object));
        var body = Expression.Call(target, setMethod,
            Expression.Convert(value, property.PropertyType));
        return Expression.Lambda<Action<T, object>>(body, target, value)
            .Compile();
    }
    else
    {
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

或者使用通用类型:

    abstract class Setter<T>
    {
        public abstract void Set(T obj, object value);
    }
    class Setter<TTarget, TValue> : Setter<TTarget>
    {
        private readonly Action<TTarget, TValue> del;
        public Setter(MethodInfo method)
        {
            del = (Action<TTarget, TValue>)
                Delegate.CreateDelegate(typeof(Action<TTarget, TValue>), method);
        }
        public override void Set(TTarget obj, object value)
        {
            del(obj, (TValue)value);
        }

    }
    static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
    {
        MethodInfo setMethod = property.GetSetMethod();
        if (setMethod != null && setMethod.GetParameters().Length == 1)
        {
            Setter<T> untyped = (Setter<T>) Activator.CreateInstance(
                typeof(Setter<,>).MakeGenericType(typeof(T),
                property.PropertyType), setMethod);
            return untyped.Set;
        }
        else
        {
            return null;
        }
    }
Run Code Online (Sandbox Code Playgroud)