Во многих проектах есть необходимость доступа к свойствам объектов используя механизм
Итак, был сделан небольшой тест. Для начала сделали 2 класса (для тестирования виртуальных и переопределенных свойств), к свойствам которых будем обращаться:
Теперь собственно тест:
Результат:
Ничего себе, в 17 раз медленнее! Хотя что-то похожее и предполагалось.
Для увеличения скорости сначала было предложено кэшировать обращения к Reflection (куда же без него в такой задаче), но серьезного ускорения это не дало. Тогда был опробован подход с генерацией кода обращения на лету, то есть:
Код генерации приводить в топике не буду, кому интересно скачивайте Проект (29Кб).
Результат выполнения:
Отличный результат! По скорости сопоставим с прямым вызовом, при нескольких запусках это время иногда было меньше 100%. Вызов назвал кэшированным так как класс с методом был запомнен и шло постоянное обращение без проверки существования такого класса.
Для завершения эксперимента был написан статический класс, который кэшировал построенные классы и создавал их по мере надобности.
Результат выполнения:
Всего на 38% медленнее прямого вызова. Несколько запусков дали средний результат 150%, но все равно это не 1746%.
Полный вывод теста:
2 миллиона вызовов:
И еще в 10 раз больше:
Что еще можно добавить:
Дополнение 1.
Диаграмма классов более продвинутой версии:

Диаграмма классов генератора кода:


System.Web.UI.DataBinder.Eval
и встал вопрос: насколько это быстро работает и можно ли выполнять такую операцию еще быстрее?Итак, был сделан небольшой тест. Для начала сделали 2 класса (для тестирования виртуальных и переопределенных свойств), к свойствам которых будем обращаться:
public class c1
{
string _str;
public c1(string str)
{
_str = str;
}
public string Str1 { get { return "1: "+_str; } }
public virtual string Str2 { get { return "2: "+_str; } }
}
public class c2 : c1
{
public c2(string str)
: base(str)
{
}
public override string Str2 { get { return "C"+base.Str2; } }
}
Теперь собственно тест:
c2 c = new c2("str");
// Прямой доступ к свойствам
for (int i=0; i<ITERATIONS; i++)
{
String.Format("Str1: {0}, Str2: {1}", c.Str1, c.Str2);
}
// Доступ через DataBinder
for (int i=0; i<ITERATIONS; i++)
{
String.Format("Str1: {0}, Str2: {1}", DataBinder.Eval(c, "Str1"), DataBinder.Eval(c, "Str2"));
}
Результат:
- Прямой вызов: 00:00:00.065 (100%)
- DataBinder.Eval: 00:00:01.135 (1746%)
Ничего себе, в 17 раз медленнее! Хотя что-то похожее и предполагалось.
Для увеличения скорости сначала было предложено кэшировать обращения к Reflection (куда же без него в такой задаче), но серьезного ускорения это не дало. Тогда был опробован подход с генерацией кода обращения на лету, то есть:
- При первом обращении изучаем класс и свойство через Reflection
- Идем к тому классу, где свойство было определено или переопределено
- Строим класс, который состоит из одного метода: получить значение нужного свойства у объекта определенного типа
- Вызываем построенный метод
Код генерации приводить в топике не буду, кому интересно скачивайте Проект (29Кб).
c2 c = new c2("str");
PropertyAccessor p1 = PEval.GetPropertyAccessor(c.GetType(), "Str1");
PropertyAccessor p2 = PEval.GetPropertyAccessor(c.GetType(), "Str2");
for (int i=0; i<ITERATIONS; i++)
{
String.Format("Str1: {0}, Str2: {1}", p1.GetValue( c ), p2.GetValue( c ));
}
Результат выполнения:
- Кэшированный вызов: 00:00:00.065 (100%)
Отличный результат! По скорости сопоставим с прямым вызовом, при нескольких запусках это время иногда было меньше 100%. Вызов назвал кэшированным так как класс с методом был запомнен и шло постоянное обращение без проверки существования такого класса.
Для завершения эксперимента был написан статический класс, который кэшировал построенные классы и создавал их по мере надобности.
c2 c = new c2("str");
for (int i=0; i<ITERATIONS; i++)
{
String.Format("Str1: {0}, Str2: {1}", PEval.GetValue(c, "Str1"), PEval.GetValue(c, "Str2"));
}
Результат выполнения:
- PEval.GetValue: 00:00:00.090 (138%)
Всего на 38% медленнее прямого вызова. Несколько запусков дали средний результат 150%, но все равно это не 1746%.
Полный вывод теста:
Create PropertyAccessor for TestDataBinding.c1.Str1
Create PropertyAccessor for TestDataBinding.c2.Str2
=========================
ITERATIONS: 100000
Прямой вызов: 00:00:00.065 (100%)
PEval.GetValue: 00:00:00.090 (138%)
Кэшированный вызов: 00:00:00.065 (100%)
DataBinder.Eval: 00:00:01.135 (1746%)
=========================
TestDataBinding.c2 (TestDataBinding.PropEvaluator.PropertyAccessor) [200003]
TestDataBinding.c1_Str1_Accessor => Str1 (G) [100002]
TestDataBinding.c2_Str2_Accessor => Str2 (G) [100001]
TestDataBinding.c1 (TestDataBinding.PropEvaluator.PropertyAccessor) [2]
TestDataBinding.c1_Str1_Accessor => Str1 (G) [100002]
=========================
2 миллиона вызовов:
=========================
ITERATIONS: 1000000
Прямой вызов: 00:00:00.930 (100%)
PEval.GetValue: 00:00:01.085 (116%)
Кэшированный вызов: 00:00:00.738 (79%)
DataBinder.Eval: 00:00:10.976 (1180%)
=========================
И еще в 10 раз больше:
=========================
ITERATIONS: 10000000
Прямой вызов: 00:00:06.802 (100%)
PEval.GetValue: 00:00:10.917 (160%)
Кэшированный вызов: 00:00:07.017 (103%)
DataBinder.Eval: 00:01:45.476 (1550%)
=========================
Что еще можно добавить:
- Сделать методы для изменения значения свойства
- Расширить класс доступа к свойству дополнительными функциями: отображаемое имя свойства, форматирование значения в строку/парсинг значения из строки, валидация значения
- Сделать вызов не одного свойства, а цепочки
PEval.GetValue(o, "Prop1.Prop2.Prop4")
Дополнение 1.
Диаграмма классов более продвинутой версии:

Диаграмма классов генератора кода:

