Improving Fody MethodDecoratorEx for asynchronous methods
The article will focus on a tiny improvement to the Fody.MethodDecorator project with the addition of the ability to decorate asynchronous methods.
Aspect-oriented programming tools such as PostSharp and Fody are widely known in narrow circles .
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 a relatively high price make us look towards alternatives.
Fody, in turn, is free, based on Mono.Cecil and comes with many plugins. You can read more about them in this article by AlexeySuvorov. One of these plugins - MethodDecorator - also a slightly improved author of the previous article - I encountered during the implementation of logging.
After loading the MethodDecoratorEx package to simplify logging (or some other kind of processing), the necessary attribute is created that inherits the IMethodDecorator interface with input, output, and exception handling methods and is hung on the necessary methods.
A method decorated with such an attribute after compilation contains code for processing the input to the method, exiting it, and catching exceptions.

But if the method is asynchronous, a problem occurs. Entering the method and exiting the method are intercepted without problems, but exceptions, if they arise, are not logged at all. This is because even an asynchronous method decorated with an attribute is translated into a special, finite-state magic, which does not allow us to catch the exception in our method.

To solve this problem, the following workaround was used - to modify MethodDecoratorEx so that it was possible to intercept the returned Task, and process it with the TaskContinuation method as follows:

Very little. The receipt of the TaskContinuation method was added, checking that the return value contains in the name of the type Task. And depending on this, the execution of three IL instructions is added.
This implementation works correctly on my tasks, but it has a couple of drawbacks. Still, the project, modified on the knee, is still a little damp.
In particular, all xUnit tests that were written for MethodDecoratorEx now crash suddenly. There is no time to deal with this yet, so if someone has a desire to rewrite the tests in the correct way, or to help, I will be glad.
You can also slightly improve the check on Task.
Project here
NuGet package here
Thank you very much for your attention.
Short introduction
Aspect-oriented programming tools such as PostSharp and Fody are widely known in narrow circles .
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 a relatively high price make us look towards alternatives.
Fody, in turn, is free, based on Mono.Cecil and comes with many plugins. You can read more about them in this article by AlexeySuvorov. One of these plugins - MethodDecorator - also a slightly improved author of the previous article - I encountered during the implementation of logging.
So, decorating methods
After loading the MethodDecoratorEx package to simplify logging (or some other kind of processing), the necessary attribute is created that inherits the IMethodDecorator interface with input, output, and exception handling methods and is hung on 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) { ... }
}
A method decorated with such an attribute after compilation contains 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 occurs. Entering the method and exiting the method are intercepted without problems, but exceptions, if they arise, are not logged at all. This is because even an asynchronous method decorated with an attribute is translated into a special, finite-state magic, which does not allow us to catch the exception in our method.
[AsyncInterceptor]
public async Task Bar()
{
throw new Exception();
}
To solve this problem, the following workaround was used - to modify MethodDecoratorEx so that it was possible to 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 was added, checking that the return value contains in the name of the type Task. And depending on this, the execution of three IL instructions is added.
private static IEnumerable 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 drawbacks. Still, the project, modified on the knee, is still a little damp.
In particular, all xUnit tests that were written for MethodDecoratorEx now crash suddenly. There is no time to deal with this yet, so if someone has a desire to rewrite the tests in the correct way, or to help, I will be glad.
You can also slightly improve the check on Task.
Project here
NuGet package here
Thank you very much for your attention.