15 Stimmen

Erstellung eines leistungsfähigen offenen Delegaten für einen Eigenschaftssetter oder -getter

Ein offener Delegat ist ein Delegat für eine Instanzmethode ohne Ziel. Um ihn aufzurufen, müssen Sie das Ziel als erstes Argument angeben. Sie sind eine clevere Möglichkeit, Code zu optimieren, der sonst Reflexion verwenden würde und eine schlechte Leistung hätte. Für eine Einführung in offene Delegaten siehe dieses. In der Praxis würden Sie teuren Reflexionscode verwenden, um diese offenen Delegaten zu erstellen, aber dann könnten Sie sie sehr preiswert als einfachen Delegatenaufruf aufrufen.

Ich versuche, Code zu schreiben, der eine beliebige PropertyInfo in einen solchen Delegaten für dessen Setter umwandelt. Bisher bin ich auf dies gekommen:

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

namespace Test
{
    class TestClass
    {
        static Action MakeSetterDelegate(PropertyInfo property)
        {
            MethodInfo setMethod = property.GetSetMethod();
            if (setMethod != null && setMethod.GetParameters().Length == 1) //überspringt unangenehme Indexeigenschaften
            {
                //Um an den Delegaten binden zu können, müssen wir einen Delegaten
                //Typ erstellen wie: Action anstatt Action.
                //Wir verwenden Reflexion, um das zu tun
                Type setterGenericType = typeof(Action<,>);
                Type delegateType = setterGenericType.MakeGenericType(new Type[] { typeof(T), property.PropertyType });
                var untypedDelegate = Delegate.CreateDelegate(delegateType, setMethod);

                //Wir verpacken den Action Delegaten in einen Action
                Action setter = (instance, value) =>
                {
                    untypedDelegate.DynamicInvoke(new object[] { instance, value });
                };
                return setter;
            }
            else
            {
                return null;
            }
        }

        int TestProp 
        {
            set
            {
                System.Diagnostics.Debug.WriteLine("Set-Methode für TestProp aufgerufen");
            }
        }

        static void Test() 
        {
            PropertyInfo property = typeof(TestClass).GetProperty("TestProp");
            Action setter = MakeSetterDelegate(property);
            TestClass instance = new TestClass();
            setter(instance, 5);
        }
    }
}

Ein ähnlicher Code würde für den Getter geschrieben werden. Es funktioniert, aber der Setter-Delegat verwendet ein DynamicInvoke, um von einem Action<derivedType> in ein Action<object> zu konvertieren, was meiner Vermutung nach einen guten Teil der Optimierung frisst, die ich erreichen möchte. Deshalb lauten die Fragen:

  1. Ist das DynamicInvoke ein echtes Problem?
  2. Gibt es einen Weg daran vorbei?

19voto

Marc Gravell Punkte 970173

DynamicInvoke wird keinen leistungsfähigen Setter erstellen. Die Reflexion gegen einen generischen Innentyp ist hier die bessere Option, da dies die Verwendung von typisierten Delegaten ermöglicht. Eine weitere Option ist DynamicMethod, aber dann müssen Sie sich um einige IL-Details kümmern.

Sie möchten sich möglicherweise HyperDescriptor ansehen, der die IL-Arbeit in eine PropertyDescriptor-Implementierung einpackt. Eine andere Möglichkeit ist die Expression API (wenn Sie .NET 3.5 oder höher verwenden):

static Action MakeSetterDelegate(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>(body, target, value)
            .Compile();
    }
    else
    {
        return null;
    }
}

Alternativ mit einem generischen Typ:

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

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

1voto

Emmanuel Punkte 5358

Ich habe einmal diese Klasse erstellt. Vielleicht hilft sie:

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

    public EntityType Entity { get; set; }

    public GetterSetter(EntityType entity, Expression> 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)
                 Delegate.CreateDelegate(typeof(Action), 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;
    }

test:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X