Создание открытого участника-исполнителя для средства настройки свойств или getter

Открытый делегат является делегатом метода экземпляра без цели. Чтобы вызвать его, вы указываете цель в качестве ее первого параметра. Это умный способ оптимизации кода, который в противном случае использовал бы отражение и имел бы низкую производительность. Для вступления в открытые делегаты см. 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);
        }
    }
}

Аналогичный код будет написан для получателя. Он работает, но делегат setter использует DynamicInvoke для преобразования из Action <derivedType > в Action <object > , который, как я подозреваю, питается хорошей частью оптимизации, которой я пользуюсь. Итак, вопросы:

  • Является ли DynamicInvoke реальной проблемой?
  • Есть ли все-таки вокруг?

Ответы

Ответ 1

DynamicInvoke не будет выполнять настройку исполнителя. Отражение от общего внутреннего типа - ваш лучший вариант здесь, так как это позволит вам использовать типизированные делегаты. Другим вариантом является DynamicMethod, но тогда вам нужно беспокоиться о нескольких деталях IL.

Вы можете посмотреть HyperDescriptor, который завершает работу IL в реализацию PropertyDescriptor. Другим вариантом является API Expression (если вы используете .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;
        }
    }

Ответ 2

Я однажды сделал этот класс. Возможно, это помогает:

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;