2010-11-03 11 views
14

Un délégué ouvert est un délégué à une méthode d'instance sans la cible. Pour l'appeler, vous fournissez la cible comme premier paramètre. Ils constituent un moyen astucieux d'optimiser le code qui, sinon, utiliserait la réflexion et aurait de mauvaises performances. Pour une introduction à l'ouverture des délégués, voir this. La façon dont vous l'utiliseriez dans la pratique est d'avoir un code de réflexion coûteux pour construire ces délégués ouverts, mais alors vous seriez en mesure de les appeler très bon marché comme un simple appel de délégué. J'essaye d'écrire le code qui transformera un PropertyInfo arbitraire, en un tel délégué pour son setter. Jusqu'ici je suis venu avec ceci:Création d'un délégué ouvert performant pour un accesseur de propriété ou un getter

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

Le code semblable serait écrit pour le getter. Cela fonctionne, mais le délégué setter utilise un DynamicInvoke pour convertir d'une action <derivedType> à l'action <object>, qui je soupçonne mange une bonne partie de l'optimisation que je suis après. Donc les questions sont:

  1. Est-ce que DynamicInvoke est une réelle préoccupation?
  2. Y at-il de toute façon autour d'elle?

Répondre

18

DynamicInvoke ne fera pas un poseur performant. La réflexion contre un type interne générique est votre meilleure option ici, car cela vous permettra d'utiliser dactylographié délégués. Une autre option est DynamicMethod, mais alors vous devez vous soucier de quelques détails IL.

Vous pourrait vouloir regarder HyperDescriptor, qui enveloppe l'IL fonctionne dans une mise en œuvre PropertyDescriptor. Une autre option est l'API Expression (si vous utilisez .NET 3.5 ou plus):

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

Ou bien avec un type générique:

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

Pouvez-vous préciser votre réponse? 1-Qu'entendez-vous par "Réflexion contre un type interne générique"; 2 - Comment l'API Expression m'aiderait-elle? –

+0

@David - Exemple d'expression ajouté. Je vais fouetter un exemple de type interne générique –

+0

@David - et ajouté l'exemple de type générique interne –

1

Une fois, je fait cette classe. Peut-être aide:

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

test:

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