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.
„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
Użyłem tego podejścia (wyrażeń), a następnie odkryłem, że 'System.Reflection.Emit.DyanamicMethod' jest znacznie prostszy. –
Wydaje mi się, że środowisko dynamiczne buforuje również tego typu rzeczy. Nie sądzę, żeby to działało o wiele wolniej. – MBoros