2010-11-03 8 views
14

Un delegato aperto è un delegato a un metodo di istanza senza la destinazione. Per chiamarlo fornisci il bersaglio come primo parametro. Sono un modo intelligente per ottimizzare il codice che altrimenti userebbe la riflessione e ha prestazioni scadenti. Per un'introduzione ai delegati aperti vedere this. Il modo in cui lo useresti in pratica è avere un costoso codice di riflessione per costruire questi delegati aperti, ma in questo caso potresti chiamarli molto a buon mercato come una semplice chiamata Delegate.Creazione di un delegato aperto performante per un setter o getter di proprietà

Sto provando a scrivere il codice che trasformerà un PropertyInfo arbitrario, in un tale delegato per il suo setter. Finora mi è venuta questa idea:

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

Il codice simile sarebbe stato scritto per il getter. Funziona, ma il delegato setter usa un DynamicInvoke per convertire da un'azione <derivedType> all'azione <object>, che sospetto stia mangiando buona parte dell'ottimizzazione che sto cercando. Quindi le domande sono:

  1. Il DynamicInvoke è una preoccupazione reale?
  2. C'è comunque intorno?

risposta

18

DynamicInvoke non farà un setter performante. La riflessione su un tipo generico interno è la tua opzione migliore qui, poiché ciò ti consentirà di utilizzare delegati. Un'altra opzione è DynamicMethod, ma poi devi preoccuparti di alcuni dettagli di IL.

È potrebbe voler guardare HyperDescriptor, che avvolge l'IL lavorare in un'implementazione PropertyDescriptor. Un'altra opzione è la Expression API (se si utilizza .NET 3.5 o superiore):

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

Oppure, in alternativa, con un tipo generico:

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

Puoi approfondire la vostra risposta? 1-Cosa intendi per "Riflessione contro un generico tipo interiore"; 2 - Come potrebbe aiutarmi l'API di Expression? –

+0

@David - esempio di espressione aggiunto. Eseguirò un esempio di tipo interno generico –

+0

@David e aggiunto l'esempio di tipo generico interno –

1

una volta ho fatto questa classe. Forse aiuta:

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

prova:

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