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

问题描述 投票:0回答:3

开放委托是对没有目标的实例方法的委托。要调用它,您需要提供目标作为其第一个参数。它们是优化代码的巧妙方法,否则会使用反射并导致性能不佳。有关开放代表的介绍,请参阅this。在实践中使用它的方式是使用昂贵的反射代码来构建这些开放委托,但随后您将能够像简单的委托调用一样非常便宜地调用它们。

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

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

将为 getter 编写类似的代码。它可以工作,但是 setter 委托使用 DynamicInvoke 从 Action

<derivedType
> 转换为 Action
<object
>,我怀疑这占用了我所追求的优化的很大一部分。所以问题是:

  1. DynamicInvoke 是一个真正值得关注的问题吗?
  2. 周围还有吗?
c# reflection delegates
3个回答
20
投票

DynamicInvoke
不会成为一名出色的二传手。在这里,针对通用内部类型的反射是更好的选择,因为这将允许您使用typed委托。另一种选择是
DynamicMethod
,但是您需要担心一些 IL 细节。

可能想查看

HyperDescriptor
,它将IL工作包装到
PropertyDescriptor
实现中。另一个选项是
Expression
API(如果您使用的是 .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;
    }
}

或者使用泛型类型:

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

1
投票

我曾经上过这门课。也许有帮助:

public class GetterSetter<EntityType,propType>
{
    private readonly Func<EntityType, propType> getter;
    private readonly Action<EntityType, propType> setter;
    private readonly string propertyName;
    private readonly Expression<Func<EntityType, propType>> propertyNameExpression;

    public EntityType Entity { get; set; }

    public GetterSetter(EntityType entity, Expression<Func<EntityType, propType>> property_NameExpression)
    {
        Entity = entity;
        propertyName = GetPropertyName(property_NameExpression);
        propertyNameExpression = property_NameExpression;
        //Create Getter
        getter = propertyNameExpression.Compile();
        // Create Setter()
        MethodInfo method = typeof (EntityType).GetProperty(propertyName).GetSetMethod();
        setter = (Action<EntityType, propType>)
                 Delegate.CreateDelegate(typeof(Action<EntityType, propType>), method);
    }


    public propType Value
    {
        get
        {
            return getter(Entity);
        }
        set
        {
            setter(Entity, value);
        }
    }

    protected string GetPropertyName(LambdaExpression _propertyNameExpression)
    {
        var lambda = _propertyNameExpression as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }
        var propertyInfo = memberExpression.Member as PropertyInfo;
        return propertyInfo.Name;
    }

测试:

var gs = new GetterSetter<OnOffElement,bool>(new OnOffElement(), item => item.IsOn);
        gs.Value = true;
        var result = gs.Value;

0
投票

这是一个老问题,但有人可能仍然会发现以下答案有用。

  1. 我认为

    DynamicInvoke
    调用使得通过
    MakeSetterDelegate
    优化的整个想法毫无用处。结果几乎等同于仅使用
    PropertyInfo.SetValue
    (就性能而言)。

  2. 问题中的

    MakeSetterDelegate
    方法基于使用
    Delegate.CreateDelegate
    API,这是正确的方法。对原始想法进行一些更改将为我们提供所需的解决方案。添加表示
    property
    类型的通用参数,分离方法并使用
    MethodInfo.MakeGenericMethod
    反射 API。这是生成的代码(带有附加说明):

     static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
     {
         // All the actual work is done in the MakeSetterDelegateCore<T, TValue> method below.
         // We can't call it directly because of an unknown second generic type argument.
         // So, we take advantage of the MethodInfo.MakeGenericMethod reflection API.
         // First, we need to get the MethodInfo instance,
         //   which is actually a so-called "GenericMethodDefinition".
         // Of course, we can use a typeof(TestClass).GetMethod(...) call to do this,
         //   but there is a more interesting, type-safe and reliable way – via delegate,
         //   which is demonstrated here:
         var method = ((Func<PropertyInfo, Action<T, object>>)MakeSetterDelegateCore<T, object>)
             .Method
             .GetGenericMethodDefinition()
             .MakeGenericMethod(new[] { typeof(T), property.PropertyType });
    
         // The performance cost of the MethodInfo.Invoke call is not an issue here:
         //   this is a one-time call compared to multiple uses of the resulting delegate.
         return (Action<T, object>)method.Invoke(null, new object[] { property });
     }
    
     private static Action<T, object> MakeSetterDelegateCore<T, TValue>(PropertyInfo property)
     {
         MethodInfo setMethod = property.GetSetMethod();
         if (setMethod != null && setMethod.GetParameters().Length == 1) //skips over nasty index properties
         {
             var typedSetter = (Action<T, TValue>)Delegate.CreateDelegate(typeof(Action<T, TValue>), setMethod);
             Action<T, object> setter = (instance, value) =>
             {
                 typedSetter(instance, (TValue)value);
             };
             return setter;
         }
    
         return null; // or perhaps it's better to throw some exception
     }
    
© www.soinside.com 2019 - 2024. All rights reserved.