2016-07-22 6 views
8

Nach den Beispielen auf this post und its follow-up question, versuche ich Feld Getter/Setter mit kompilierten Ausdrücke zu erstellen.Feld Getter/Setter mit Ausdrucksbaum in der Basisklasse

Der Getter funktioniert einfach großartig, aber ich bin fest der Setter, wie ich den Setter, um jede Art von Feldern zuweisen müssen.

Hier meine Setter-Aktion Bauer:

public static Action<T1, T2> GetFieldSetter<T1, T2>(this FieldInfo fieldInfo) { 
    if (typeof(T1) != fieldInfo.DeclaringType && !typeof(T1).IsSubclassOf(fieldInfo.DeclaringType)) { 
    throw new ArgumentException(); 
    } 
    ParameterExpression targetExp = Expression.Parameter(typeof(T1), "target"); 
    ParameterExpression valueExp = Expression.Parameter(typeof(T2), "value"); 
    // 
    // Expression.Property can be used here as well 
    MemberExpression fieldExp = Expression.Field(targetExp, fieldInfo); 
    BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp); 
    // 
    return Expression.Lambda<Action<T1, T2>> (assignExp, targetExp, valueExp).Compile(); 
} 

Nun speichere ich die allgemeinen Setter in einen Cache-Liste (weil natürlich jedes Mal die Setter-Gebäude ist ein Leistungskiller), wo ich warf sie als einfach "Objekte":

// initialization of the setters dictionary 
Dictionary<string, object> setters = new Dictionary(string, object)(); 
Dictionary<string, FieldInfo> fldInfos = new Dictionary(string, FieldInfo)(); 
FieldInfo f = this.GetType().GetField("my_int_field"); 
setters.Add(f.Name, GetFieldSetter<object, int>(f); 
fldInfos.Add(f.Name, f); 
// 
f = this.GetType().GetField("my_string_field"); 
setters.Add(f.Name, GetFieldSetter<object, string>(f); 
fldInfos.Add(f.Name, f); 

Jetzt versuche ich so einen Feldwert zu setzen:

void setFieldValue(string fieldName, object value) { 
     var setterAction = setters[fieldName]; 
     // TODO: now the problem => how do I invoke "setterAction" with 
     // object and fldInfos[fieldName] as parameters...? 
} 

I Ich könnte einfach eine generische Methode aufrufen und jedes Mal umwandeln, aber ich mache mir Sorgen um den Leistungsaufwand ... Irgendwelche Vorschläge?

- EDITED ANTWORT Basierend auf Mr Anderson's answer, habe ich ein kleines Testprogramm, das direkt den Wert vergleicht Einstellung zwischengespeichert Reflexion (wo Fieldinfo des zwischengespeichert werden) und die im Cache gespeicherten Multi-Typ-Code. Ich verwende Objektvererbung mit bis zu 3 Vererbungsebenen (ObjectC : ObjectB : ObjectA).

Full code is of the example can be found here.

Einzel Iteration des Tests folgende Ausgabe ergibt:

------------------------- 
---  OBJECT A  --- 
------------------------- 
    Set direct:  0.0036 ms 
    Set reflection: 2.319 ms 
    Set ref.Emit:  1.8186 ms 
    Set Accessor:  4.3622 ms 

------------------------- 
---  OBJECT B  --- 
------------------------- 
    Set direct:  0.0004 ms 
    Set reflection: 0.1179 ms 
    Set ref.Emit:  1.2197 ms 
    Set Accessor:  2.8819 ms 

------------------------- 
---  OBJECT C  --- 
------------------------- 
    Set direct:  0.0024 ms 
    Set reflection: 0.1106 ms 
    Set ref.Emit:  1.1577 ms 
    Set Accessor:  2.9451 ms 

Natürlich ist dies zeigt einfach die Kosten für die Objekte zu schaffen - das ist uns die Offset der Erstellung der im Cache gespeicherten Versionen messen kann der Reflexion und der Ausdrücke.

Als nächstes wollen wir mal 1.000.000 laufen:

------------------------- 
---  OBJECT A  --- 
------------------------- 
    Set direct:  33.2744 ms 
    Set reflection: 1259.9551 ms 
    Set ref.Emit:  531.0168 ms 
    Set Accessor:  505.5682 ms 

------------------------- 
---  OBJECT B  --- 
------------------------- 
    Set direct:  38.7921 ms 
    Set reflection: 2584.2972 ms 
    Set ref.Emit:  971.773 ms 
    Set Accessor:  901.7656 ms 

------------------------- 
---  OBJECT C  --- 
------------------------- 
    Set direct:  40.3942 ms 
    Set reflection: 3796.3436 ms 
    Set ref.Emit:  1510.1819 ms 
    Set Accessor:  1469.4459 ms 

Aus Gründen der Vollständigkeit halber: ich den Aufruf der „set“ -Methode entfernt, um die Kosten des Erhaltens der Setter (FieldInfo für das Reflexionsverfahren zu markieren , Action<object, object> für den Ausdruck case). Hier die Ergebnisse:

------------------------- 
---  OBJECT A  --- 
------------------------- 
    Set direct:  3.6849 ms 
    Set reflection: 44.5447 ms 
    Set ref.Emit:  47.1925 ms 
    Set Accessor:  49.2954 ms 


------------------------- 
---  OBJECT B  --- 
------------------------- 
    Set direct:  4.1016 ms 
    Set reflection: 76.6444 ms 
    Set ref.Emit:  79.4697 ms 
    Set Accessor:  83.3695 ms 

------------------------- 
---  OBJECT C  --- 
------------------------- 
    Set direct:  4.2907 ms 
    Set reflection: 128.5679 ms 
    Set ref.Emit:  126.6639 ms 
    Set Accessor:  132.5919 ms 

HINWEIS: Zeit Anstieg ist hier nicht aufgrund der Tatsache, dass die Zugriffszeiten langsamer sind für größere Wörterbücher (wie sie O(1) Zugriffszeiten), aber aufgrund der Tatsache, dass die Anzahl der Male, die wir Zugriff darauf wird erhöht (4 mal pro Iteration für ObjectA, 8 für ObjectB, 12 für ObjectC) ... Wie man sieht, macht hier nur der Erstellungsoffset einen Unterschied (was zu erwarten ist).

Fazit: Wir haben die Leistung um einen Faktor von 2 oder mehr verbessert, aber wir sind immer noch weit von der Leistung des direkten Feldsatzes entfernt ... Das Auffinden des richtigen Setter in der Liste entspricht gut 10% der Zeit .

Ich werde mit Ausdrucksbäumen anstelle von Reflection.Emit versuchen, um zu sehen, ob wir die Lücke weiter reduzieren können ... Jeder Kommentar ist mehr als willkommen.

EDIT 2 I addierten Ergebnisse der Ansatz unter Verwendung eines generischen "Accessor"-Klasse als durch Eli Arbel auf this post vorgeschlagen.

+3

„Besorgt über die Leistung“ nicht ganz es geschnitten. Testen Sie es, sehen Sie, ob es gut genug funktioniert, und entscheiden Sie sich danach. Ich sehe keinen Grund, warum die Verwendung einer generischen Methode schlechter wäre als Ihre derzeitige Vorgehensweise. – Luaan

+0

Ich verwendete diesen Ansatz (Ausdrücke), dann entdeckte ich 'System.Reflection.Emit.DyanamicMethod' ist viel einfacher. –

+0

Ich denke, die Dynamische Laufzeit speichert auch solche Sachen. Ich glaube nicht, dass es so viel langsamer würde. – MBoros

Antwort

1

Wenn Sie möchten, dass dies Operationen für mehrere Typen unterstützt, sollte Ihr Funktionscache durch Type UND Feldname (string) indiziert werden, und die Funktionen sollten langsam erstellt werden. Versuchen Sie folgendes:

private static Dictionary<Type, Dictionary<string, Action<object, object>>> _typeMapper = new Dictionary<Type, Dictionary<string, Action<object, object>>>(); 

public static void Set(object obj, string fieldName, object newValue) 
{ 
    if (obj == null) 
    { 
     throw new ArgumentNullException("obj"); 
    } 
    Type type = obj.GetType(); 
    Dictionary<string, Action<object, object>> fieldMapper; 
    Action<object, object> action; 
    if (_typeMapper.TryGetValue(type, out fieldMapper)) 
    { 
     // entry has been created for this type. 
     if (!fieldMapper.TryGetValue(fieldName, out action)) 
     { 
      // method has not been created yet, must build it. 
      FieldInfo fld = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 
      if (fld == null) 
      { 
       throw new ArgumentException("No field " + fieldName); 
      } 
      action = buildSetter(fld); 
      fieldMapper.Add(fieldName, action); // add it to method cache for future use. 
     } 
    } 
    else 
    { 
     // -- ADDED CODE: forgot to create the new fieldMapper..... 
     fieldMapper = new Dictionary<string, Action<object, object>>(); 

    // type has not been added yet, so we know method has not been built yet either. 
     FieldInfo fld = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 
     if (fld == null) 
     { 
      throw new ArgumentException("No field " + fieldName); 
     } 
     action = buildSetter(fld); 
     fieldMapper.Add(fieldName, action); // add it to method cache for future use. 
     _typeMapper.Add(type, fieldMapper); // add it to type cache for future use. 
    } 
    action(obj, newValue); // invoke the method. 
} 
// this is my preferred setter-builder, feel free to use expressions instead. 
private static Action<object, object> buildSetter(FieldInfo fld) 
{ 
    DynamicMethod dyn = new DynamicMethod("set_" + fld, typeof(void), new[] { typeof(object), typeof(object) }, fld.DeclaringType); 
    ILGenerator gen = dyn.GetILGenerator(); 
    gen.Emit(OpCodes.Ldarg_0); 
    gen.Emit(OpCodes.Castclass, fld.DeclaringType); 
    gen.Emit(OpCodes.Ldarg_1); 
    if (fld.FieldType.IsClass) 
    { 
     gen.Emit(OpCodes.Castclass, fld.FieldType); 
    } 
    else 
    { 
     gen.Emit(OpCodes.Unbox_Any, fld.FieldType); 
    } 
    gen.Emit(OpCodes.Stfld, fld); 
    gen.Emit(OpCodes.Ret); 
    return (Action<object, object>)dyn.CreateDelegate(typeof(Action<object, object>)); 
} 

Andernfalls, wenn Sie nur brauchen, tun dies mit einer Art, Ihr Prozess wird:

private static Dictionary<string, Action<MyType, object>> _mapper = new Dictionary<string, Action<MyType, object>>(); 

public static void Set(MyType obj, string fieldName, object newValue) 
{ 
    if (obj == null) 
    { 
     throw new ArgumentNullException("obj"); 
    } 
    Action<MyType, object> action; 
    if (!_mapper.TryGetValue(fieldName, out action)) 
    { 
     FieldInfo fld = typeof(MyType).GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 
     if (fld == null) 
     { 
      throw new ArgumentException("No field " + fieldName); 
     } 
     action = buildSetter(fld); 
     _mapper.Add(fieldName, action); 
    } 
    action(obj, newValue); // invoke the method. 
} 

private static Action<MyType, object> buildSetter(FieldInfo fld) 
{ 
    DynamicMethod dyn = new DynamicMethod("set_" + fld, typeof(void), new[] { typeof(MyType), typeof(object) }, typeof(MyType)); 
    ILGenerator gen = dyn.GetILGenerator(); 
    gen.Emit(OpCodes.Ldarg_0); 
    gen.Emit(OpCodes.Ldarg_1); 
    if (fld.FieldType.IsClass) 
    { 
     gen.Emit(OpCodes.Castclass, fld.FieldType); 
    } 
    else 
    { 
     gen.Emit(OpCodes.Unbox_Any, fld.FieldType); 
    } 
    gen.Emit(OpCodes.Stfld, fld); 
    gen.Emit(OpCodes.Ret); 
    return (Action<MyType, object>)dyn.CreateDelegate(typeof(Action<MyType, object>)); 
} 
+0

Große Antwort, danke. Ich werde ein kleines Leistungstestprogramm schreiben, um die Leistungen beider zu überprüfen und es hier zu veröffentlichen. – neggenbe

Verwandte Themen