2016-07-22 31 views
8

Następujące przykłady na temat this post i its follow-up question, próbuję utworzyć linie pobierające/ustawiające pola za pomocą wyrażeń kompilowanych.Pole pobierające/ustawiające z drzewem wyrażeń w klasie bazowej

Getter działa świetnie, ale utknąłem w setera, ponieważ potrzebuję ustawiacza, aby przypisać dowolny rodzaj pól.

Oto mój budowniczy seter-action:

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

Teraz przechowywać rodzajowe ustawiające do listy podręcznej (bo oczywiście, budowanie setter każdym razem jest zabójcą wydajność), gdzie rzucam je jako proste "obiekty":

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

teraz próbuję ustawić wartość pola tak:

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 można po prostu wywołać metodę generyczną i rzutować za każdym razem, ale martwię się o obciążenie związane z wydajnością ... Jakieś sugestie?

- EDITED ODPOWIEDŹ podstawie Mr Anderson's answer, stworzyłem mały program testowy, który porównuje bezpośrednio ustawiając wartość, buforowane odbicie (gdzie FieldInfo użytkownika są buforowane) i buforowane kod wielo-type. Używam dziedziczenia obiektów z maksymalnie 3 poziomem dziedziczenia (ObjectC : ObjectB : ObjectA).

Full code is of the example can be found here.

pojedyncze powtórzenie testu daje następujące dane wyjściowe:

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

Oczywiście, to po prostu pokazuje, że koszt stworzenia obiektów - to pozwala nam mierzyć przesunięcie tworzenia pamięci podręcznej wersje Refleksji i Wyrażeń.

Następnie niech uruchomić 1.000.000 razy:

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

Dla kompletności: usunąłem wezwanie do metody „set”, aby podświetlić koszt uzyskania setter (FieldInfo dla metody refleksji , Action<object, object> dla przypadku wyrażeń). Oto wyniki:

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

UWAGA: zwiększenie czasu nie jest tu ze względu na fakt, że czasy dostępu są wolniejsze dla większych słowników (jak mają O(1) czasy dostępu), ale ze względu na fakt, że wiele razy my dostęp jest zwiększany (4 razy na iterację dla ObjectA, 8 dla ObjectB, 12 dla ObjectC) ... Jak widać, tylko przesunięcie kreacji ma tutaj znaczenie (czego można się spodziewać).

Podsumowując: poprawiliśmy wydajność o współczynnik 2 lub więcej, ale nadal jesteśmy daleko od wydajności zestawu bezpośredniego pola ... Odtworzenie odpowiedniego setera na liście stanowi dobre 10% czasu .

Spróbuję z drzewkami ekspresji zamiast Reflection.Emitować, czy możemy dalej zmniejszyć lukę ... Każdy komentarz jest więcej niż mile widziany.

EDIT 2 dodałem wyniki stosując podejście Korzystanie z ogólnej klasy „akcesor” jak sugeruje Eli Arbel na this post.

+3

„martwi się o wydajność” nie całkiem wyciąć. Przetestuj to, sprawdź, czy działa wystarczająco dobrze i zdecyduj na podstawie tego. Nie widzę powodu, dla którego stosowanie ogólnej metody byłoby gorsze niż obecne podejście. – Luaan

+0

Użyłem tego podejścia (wyrażeń), a następnie odkryłem, że 'System.Reflection.Emit.DyanamicMethod' jest znacznie prostszy. –

+0

Wydaje mi się, że środowisko dynamiczne buforuje również tego typu rzeczy. Nie sądzę, żeby to działało o wiele wolniej. – MBoros

Odpowiedz

1

Jeśli chcesz, aby wspierało to operacje na wielu typach, pamięć podręczna funkcji powinna być indeksowana przez Type I nazwę pola (string), a funkcje powinny być leniwie tworzone. Spróbuj tego:

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

W przeciwnym razie, jeśli trzeba to zrobić tylko z jednego rodzaju, twój proces staje się:

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

Świetna odpowiedź, dzięki. Napiszę program testowy wydajności, by sprawdzić występy obu i opublikować go tutaj. – neggenbe