Улучшаем Fody MethodDecoratorEx для асинхронных методов

от автора

В статье речь пойдет о крошечном усовершенствовании проекта Fody.MethodDecorator с добавлением возможности декорирования асинхронных методов.

Небольшое предисловие

В узких кругах широко известны такие инструменты аспектно-ориентированного программирования, как PostSharp и Fody.

Первый является условно-бесплатной утилитой и, на мой взгляд, крайне ограничен в своей бесплатной версии, в частности, его нельзя применить к проектам Windows Store, использовать автоматические INotifyPropertyChanged более чем в 10 классах, и так далее. Многие из этих ограничений и относительно высокая цена заставляют смотреть в сторону альтернатив.

Fody же, в свою очередь бесплатен, основан на Mono.Cecil и снабжен множеством плагинов. Более подробно о них можно прочитать в этой статье пользователя AlexeySuvorov. С одним из этих плагинов — MethodDecorator — тоже немного усовершенствованным автором предыдущей статьи — я столкнулся во время реализации логгирования.

Итак, декорирование методов

После загрузки пакета MethodDecoratorEx для упрощения логгирования (или еще какой-нибудь обработки) создается необходимый атрибут, который наследует интерфейс IMethodDecorator с методами входа, выхода и обработки исключения и навешивается на необходимые методы.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Assembly | AttributeTargets.Module)] public class AsyncInterceptorAttribute : Attribute, IMethodDecorator {     public void Init(object instance, MethodBase methodBase, object[] args) { ... }      public void OnEntry() { ... }     public void OnExit() { ... }     public void OnException(Exception exception) { ... } } 

Украшенный таким атрибутом метод после компиляции содержит код обработки входа в метод, выхода из него и перехвата исключений.

[AsyncInterceptor] public string Bar() {     return "Hi"; } 

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

[AsyncInterceptor] public async Task<string> Bar() {     throw new Exception(); } 

Для решения этой проблемы был использован следующий обходной путь — модифицировать MethodDecoratorEx таким образом, чтобы можно было перехватить возвращаемый Task, и обработать его методом TaskContinuation следующим образом:

public void TaskContinuation(Task task) {     task.ContinueWith(OnTaskFaulted, TaskContinuationOptions.OnlyOnFaulted);     task.ContinueWith(OnTaskCancelled, TaskContinuationOptions.OnlyOnCanceled);     task.ContinueWith(OnTaskCompleted, TaskContinuationOptions.OnlyOnRanToCompletion); }  private void OnTaskFaulted(Task t) { ... } private void OnTaskCancelled(Task t) { ... } private void OnTaskCompleted(Task t) { ... } 

Что изменилось в проекте MethodDecoratorEx?

Очень мало. Были добавлены получение метода TaskContinuation, проверка на то, что возвращаемое значение содержит в имени типа Task. И в зависимости от этого добавлено выполнение трех инструкций IL.

private static IEnumerable<Instruction> GetTaskContinuationInstructions(     ILProcessor processor,      VariableDefinition retvalVariableDefinition,     VariableDefinition attributeVariableDefinition,     MethodReference taskContinuationMethodReference) {     if (retvalVariableDefinition == null) return new Instruction[0];     var tr = retvalVariableDefinition.VariableType;      if (tr.FullName.Contains("Task"))     {         return new[]         {             processor.Create(OpCodes.Ldloc_S, attributeVariableDefinition),             processor.Create(OpCodes.Ldloc_S, retvalVariableDefinition),             processor.Create(OpCodes.Callvirt, taskContinuationMethodReference),         };     }      return new Instruction[0]; } 

Данная реализация корректно работает на моих задачах, но у нее есть пара недостатков. Все-таки проект, модифицированный на коленке, еще немного сыроват.

В частности, все xUnit тесты, которые были написаны для MethodDecoratorEx, теперь внезапно падают. Разбираться с этим пока нет времени, поэтому если у кого-то возникнет желание переписать тесты корректным образом, или помочь, буду рад.

Также можно немного усовершенствовать проверку на Task.

Проект тут
NuGet пакет тут

Большое спасибо за внимание.

ссылка на оригинал статьи http://habrahabr.ru/post/263511/