Следуя примерам this post и its follow-up question, я пытаюсь создать полевые геттеры/сеттеры с использованием скомпилированных выражений.Полевой геттер/сеттер с деревом выражений в базовом классе
Геттер работает просто отлично, но я застрял сеттер, так как мне нужно, чтобы сеттер назначал любые типы полей.
Вот мой сеттер действия строитель:
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();
}
Теперь хранить общие сеттеры в список кэша (потому что, конечно, строить сеттер каждый раз, когда это производительность убийца), где я бросил их в качестве простые "объекты":
// 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);
Теперь я пытаюсь установить значение поля, как это:
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...?
}
Я мог бы просто вызвать общий метод и бросить каждый раз, но меня беспокоит производительность накладных расходов ... Любые предложения?
- EDITED ОТВЕТ Основываясь на Mr Anderson's answer, я создал небольшую тестовую программу, которая сравнивает непосредственно устанавливающее значение, кэшированные отражение (где FieldInfo находятся в кэше) и кэшируются код нескольких типов. Я использую наследование объектов с до 3 уровнями наследования (ObjectC : ObjectB : ObjectA
).
Full code is of the example can be found here.
Одно повторения теста дает следующий вывод:
-------------------------
--- 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
Конечно, это просто показывает стоимость создания объектов - это позволяет измерить смещение создания кэшированных версий отражений и выражений.
Далее, давайте запустим 1.000.000 раз:
-------------------------
--- 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
Для полноты картины: я снял вызов к «набор» метод, чтобы выделить затраты на получение сеттера (FieldInfo
для метода отражения , Action<object, object>
для случая выражения). Вот результаты:
-------------------------
--- 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
Примечание: увеличение времени здесь не из-за того, что время доступа медленнее для больших словарей (так как они имеют O(1)
время доступа), но в связи с тем, что число раз доступ к нему увеличен (4 раза за итерацию для ObjectA
, 8 для ObjectB
, 12 для ObjectC
) ... Как видим, здесь имеет место только смещение создания (что и следовало ожидать).
Итог: мы улучшили производительность в 2 раза или более, но мы все еще далеки от производительности прямого поля ... Получение правильного сеттера в списке составляет 10% от времени ,
Я попытаюсь использовать деревья выражений вместо Reflection.Emit, чтобы увидеть, можем ли мы еще больше уменьшить пробел ... Любые комментарии более чем приветствуются.
EDIT 2 Я добавил результатов, используя подход, используя универсальный класс «Accessor» как это было предложено Eli Arbel на this post.
«Обеспокоенные производительность» не совсем резать. Протестируйте его, посмотрите, хорошо ли он работает, и решите на основе этого. Я не вижу причин, почему использование общего метода было бы хуже, чем ваш текущий подход. – Luaan
Я использовал этот подход (выражения), затем я обнаружил, что 'System.Reflection.Emit.DyanamicMethod' гораздо более прост. –
Я думаю, что Dynamic runtime кэширует и такие вещи. Я не думаю, что это будет работать намного медленнее. – MBoros