В последней версии .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(); }
Если поставить в этом методе точку останова и посмотреть на строковое представление переменной 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); }
Для проверки того, насколько эта логика эффективна, разработаем небольшой тест:
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)); }
Ну и результаты говорят сами за себя:
Как видим, такой способ оптимизации более чем жизнеспособен :)
Опубликовать | Tweet |
Гы, мы какараз этот солюшен заюзали, я какраз планировал писать про это пост ;), хорошо что наткнулся на ваш ;). Теперь будет меньше писать.
ОтветитьУдалитьНе работает на Guid, DateTime
ОтветитьУдалить