понедельник, 26 сентября 2011 г.

Проблема extension-методов с типом dynamic

"Динамический тип" появился в .NET 4.0. Суть его состоит в привязывании методов не на уровне компиляции, а на уровне исполнения. Почитать про него можно к примеру тут http://habrahabr.ru/blogs/net/43773/

Но ужасное открытие меня ожидало при разработке библиотеки с использованием типов dynamic. "Расширительные" методы на нем не только не работают, но даже не выдают ошибки. Точнее, генерируют исключение уже во время работы.

Хотя такое поведение логично (тип-то динамический), в коде оно создает дикую путаницу, особенно при рефакторинге. Выхода три:
1. Не использовать extension методы
2. Не использовать dynamic типы
3. Терпеть как есть, сохранять бдительность и качественно тестировать код. Как вариант - можно вызывать методы расширения как обычные статические методы для dynamic:

dynamic dynamicObject = new object();
object obj = new object();

// вызов расширения для object
obj.ExtensionMethod();

// вызов расширения для dynamic
ObjectExtensions.ExtensionMethod(dynamicObject);

Если писать так - то все работает. Но профита в методах расширения уже нет

Читать дальше...

Вызов методов с Generic параметрами через Reflection

Допустим, есть метод с Generic-параметром

public void GenericMethod<T>()
{
}

и есть метод с параметром типа Type

public void TypeMethod(Type type)
{
}

Вызвать TypeMethod из GenericMethod не составляет труда:

public void GenericMethod<T>()
{
TypeMethod(typeof(T));
}

А как вызвать GenericMethod из TypeMethod?
Поможет Reflection


public static object InvokeGenericMethod(object obj, string methodName, Type genericParam, params object[] pars)
{
var methodInfo = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
var method = methodInfo.MakeGenericMethod(new[] { genericParam });

return method.Invoke(obj, pars);
}

С помощью этого статического метода можно вызвать любой метод любого класса с одним Generic-параметром. Ну и если Generic-параметров больше:

public static object InvokeGenericMethod(object obj, string methodName, Type[] genericParams, params object[] pars)
{
var methodInfo = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
var method = methodInfo.MakeGenericMethod(genericParams);

return method.Invoke(obj, pars);
}

Пример использования:

public void GenericMethod<T>(int param1, object param2)
{
}

public void TypeMethod(Type type, int param1, object param2)
{
InvokeGenericMethod(this, "GenericMethod", type, param1, param2);
}


Читать дальше...

пятница, 23 сентября 2011 г.

АОП на .NET без Unity

Очередная попытка вернуться к повседневному использованию аспектно-ориентированного проектирования в проектах на .NET не увенчалась успехом. Причина проста: языковые средства слабо приспособлены для этого. Как не придумывай красивое и казалось бы надежное решение, но хоть в одном месте оно дает слабину - а значит для серьезных задач не подходит.

Рассматривались три варианта решения:

1. Транспарентные прокси
Суть метода в том, что посредством Reflection мы создаем новый тип, который наследуется от нужного интерфейса и реализует прозрачный прокси-класс, выполняющий аспекты перед вызовом.
Этот метод посредством контейнера Unity я попробовал применить на живом проекте и у меня осталась масса впечатлений :)
http://fullfeedbag.blogspot.com/2010/09/aop-application-unity.html


Плюсы: после некоторых мучений АОП все же работает

Минусы: как по мне, то их оочень много
Самая большая беда: если что то сделать не так - аспекты могут отвалиться. Это не сильно страшно скажем для логирования. Но вот если на аспектах построен контроль синхронизации потоков, механизмы валидации или уровни доступа при авторизации, то отвалившиеся аспекты сделают целевую систему жутко уязвимой.
А сделать что то не так, к сожалению, очень даже легко, поскольку транспарентный прокси генерируется внешним компонентом, и если случайно его создать скажем с помощью оператора new - то аспекты не работают.
Вторая проблема: упавшая производительность. Падает она действительно очень сильно
Третья проблема: отладка кода сильно затрудняется. Отладчик Visual Studio неохотно ест сгенерированные в runtime типы данных
Четвертая проблема: багоемкость и высокий порог понимания для программистов. Объяснить принципы работы транспарентных прокси новому человеку, пришедшему на проект, ох как непросто. В совокупности с отваливающимися аспектами и диковатой отладкой это превращается в сущий ад


2. Модификация сборок в рантайме
Вообще, эта идея мне пришла сразу как только я начал применять АОП :) Мы просто берем и перехватываем метод класса. Без всяких прокси и прочей мути.
Один хороший человек разработал библиотечку для перехвата методов
http://www.codeproject.com/KB/dotnet/CLRMethodInjection.aspx
Если чуть пораскинуть мозгами - то ее можно применить для реализации АОП

Плюсы: решается проблема с быстродействием. Отваливания аспектов при грамотной реализации интерцептора не будет

Минусы:
1. Отладка еще хуже чем в случае с транспарентными прокси
2. Из-за того что метод использует грязные хаки чуть менее чем полностью, он очень сильно завязывается на версии .net framework и на его внутренних структурах данных. Проблема при переносе кода x32/x64. Если что то не так - программа падает
В общем для коммерческого проекта такой метод применять не очень хочется. Но сам по себе он очень интересен. Вот еще годные линки по теме покопаться в недрах дотнета:
http://www.codeproject.com/KB/dotnet/DotnetInternals_Injection.aspx
http://msdn.microsoft.com/en-us/magazine/cc163791.aspx
http://ntcore.com/files/disasmsil.htm


3. Модификация сборок до загрузки с помощью Mono.Cecil
Ахтунг! Не смотря на название либы Mono.Cecil, к самому Mono она, по видимому, не имеет отношения


Замысел в том, чтобы пофиксать сборочку один раз на диске и прикрутить к ней почти полноценное АОП. Библиотека Mono.Cecil позволит нам модифицировать сборки не копаясь в памяти и во внутренней структуре.


Плюсы: такие же как у всех - работающее АОП в результате
По поводу отладки затрудняюсь сказать (не пробовал), но думаю что с ней такие же проблемы как и у предыдущих методов


Минусы:
1. Фиксап подписанной (signed) сборки приводит к слетанию ЭЦП и неработоспособности сборки (кстати предыдущий метод был лишен такой проблемы).
Лечится тем, что нужно запустить процедуру фиксапа сразу после компиляции, ДО операции подписывания DLL. Это подразумевает то, что DLL своего производства. С чужими DLL такое не прокатывает

2. Операцию инжекции аспектов в сборку нужно запускать отдельно от кода. Если ее случайно не запустить - ошибка не выдается, аспекты не работают.
Введение этапа посткомпиляции - достаточно некрасивое решение. Хотя что то подобное сделали для прекомпиляции в Qt (MOC), так что может не все так плохо?

У этого метода есть приличные готовые реализации:
http://habrahabr.ru/blogs/net/95211/


Мораль сей басни состоит в том, что таки-нету идеальной реализации АОП на .NET
Так что ждем поддержки средств АОП от Microsoft на уровне компилятора. Ну или хотя бы stable версию АОП-фреймверка с Mono.Cecil


P.S. Я забыл упомянуть о PostSharp. Он использует и транспарентные прокси, и модификацию IL кода. Но, блин, стОит он дороговато...

P.P.S Литература по теме применения аспектно-ориентированного программирования:
http://www.javable.com/columns/aop/workshop/01/
http://www.javable.com/columns/aop/workshop/02/
http://citforum.ru/internet/javascript/aop/

Читать дальше...