Pull to refresh

Expression Trees и оптимизация Reflection

Reading time 3 min
Views 6.5K
В последней версии .NET Framework среди новых возможностей было добавлено средства метапрограммирования под названием Expression Trees. На базе этой технологии, а именно основываясь на том принципе, что выражения на "обычном" языке программирования могут автоматически преобразовываться в синтаксические деревья, была разработана технология LINQ.

Но в этом посте речь пойдет о другой области применения возможности динамически собирать expression trees и компилировать их в работоспособный код. И эта область — оптимизация Reflection.

кросс-пост с персонального блога


Как известно, платой за гибкость при использовании рефлексии является производительность. Но в случае, когда она применяется к некому фиксированному набору метаданных, ее легко оптимизировать.

Но динамические Expression Trees предоставляют нам еще достаточно элегантный способ оптимизации. Суть его заключается в том, что для доступа к свойствам экземпляров известного класса мы будем генерировать соответствующий строго типизированный код в виде лямбда-функции, которая будет обращаться к ним напрямую и которую мы будем кэшировать для последующего повторного использования.

Сам по себе метод создания нужной лямбда-функции достаточно прост:

private static Func<object, object> CreateGetter(object entity, string propertyName) 

  var param = Expression.Parameter(typeof (object), «e»); 
  Expression body = Expression.PropertyOrField(Expression.TypeAs(param, entity.GetType()), propertyName); 
  var getterExpression = Expression.Lambda<Func<object, object>>(body, param); 
  return getterExpression.Compile(); 
}
* This source code was highlighted with Source Code Highlighter.


Если поставить в этом методе точку останова и посмотреть на строковое представление переменной getterExpression, то мы увидим, во что оно будет скомпилировано:



Обернем всю логику доступа к свойству класса в некий ReflectionHelper, который в дальнейшем можно будет расширить методами для вызова методов, инициализации свойств и т.д. Этот класс будет реализовывать метод GetPropertyValue следующим образом:

readonly Dictionary<PropertyGetterKey, Func<object, object>> propertyGetters = new Dictionary<PropertyGetterKey, Func<object, object>>();

public object GetPropertyValue(object entity, string propertyName)
{
  Func<object, object> getter;

  var key = new PropertyGetterKey {Type = entity.GetType(), PropertyName = propertyName};

  if (propertyGetters.ContainsKey(key))
    getter = propertyGetters[key];
  else
  {
    getter = CreateGetter(entity, propertyName);
    propertyGetters.Add(key, getter);
  }

  return getter(entity);
}
* This source code was highlighted with Source Code Highlighter.


Для проверки того, насколько эта логика эффективна, разработаем небольшой тест:

var entities = new List<Class1>();

for (var i = 0; i < 20; i++)
  entities.Add(new Class1 { Property1 = «Value» + i });

foreach (var entity in entities)
{
  var start = DateTime.Now.Millisecond;
  var val = ReflectionHelper.Instance.GetPropertyValue(entity, «Property1»);
  Console.WriteLine("{0} — {1}", val, (DateTime.Now.Millisecond — start));
}
* This source code was highlighted with Source Code Highlighter.


Ну и результаты говорят сами за себя:



Как видим, такой способ оптимизации более чем жизнеспособен :)

Tags:
Hubs:
+20
Comments 29
Comments Comments 29

Articles