The article focuses on a tiny improvement on the Fody.MethodDecorator project with the addition of the ability to decorate asynchronous methods.
Small preface
In narrow circles,
aspect-oriented programming tools such as PostSharp and Fody are widely known.
The first is a shareware utility and, in my opinion, is extremely
limited in its free version , in particular, it cannot be applied to Windows Store projects, use automatic INotifyPropertyChanged in more than 10 classes, and so on. Many of these restrictions and the relatively high price make you look towards alternatives.
Fody, in turn, is free, based on Mono.Cecil and equipped with many plug-ins. More details about them can be found in
this article by
AlexeySuvorov . I encountered MethodDecorator with one of these plugins, which was also slightly improved by the previous article I encountered during the implementation of logging.
')
So, decorating methods
After loading the MethodDecoratorEx package, to simplify logging (or some other processing), the necessary attribute is created, which inherits the IMethodDecorator interface with input methods, output methods, and exception handling and is hooked to the necessary methods.
[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) { ... } }
The method decorated with such an attribute after compilation contains the code for processing the input to the method, exiting it and catching exceptions.
[AsyncInterceptor] public string Bar() { return "Hi"; }

But if the method is asynchronous, a problem arises. The input to the method and the output from the method are intercepted without problems, but exceptions, if they arise, are not logged at all. This happens because even an asynchronous method decorated with an attribute is translated into special, finite automaton magic, which does not allow to catch the exception in our method.
[AsyncInterceptor] public async Task<string> Bar() { throw new Exception(); }

To solve this problem, the following workaround was used - modify MethodDecoratorEx so that you can intercept the returned Task, and process it with the TaskContinuation method as follows:
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) { ... }

What has changed in the MethodDecoratorEx project?
Very little. The receipt of the TaskContinuation method has been added, checking that the return value contains the name of the Task type. And depending on this, the execution of three IL instructions has been added.
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]; }
This implementation works correctly on my tasks, but it has a couple of flaws. Still, the project, modified on the knee, a little more damp.
In particular, all xUnit tests that were written for MethodDecoratorEx are now suddenly falling. There is no time to deal with this, so if someone has a desire to rewrite the tests correctly, or to help, I will be glad.
You can also slightly improve the check on Task.
Project
hereNuGet package
hereThank you very much for your attention.