Why I switched from Fody to Metalama for method caching
Having previously written about implementing caching aspects with Fody, I wanted to share my experience switching to Metalama and why I believe it offers significant advantages for Metalama users looking to implement caching in their applications.
About the author
Cyril Canovas is a contributor at GoatReview 🐐, a technical blog focused on sharing in-depth developer experiences and best practices in .NET development. At GoatReview, we believe in providing practical, hands-on insights backed by real-world implementations. Our recent exploration of Aspect-Oriented Programming tools led us to evaluate different solutions for implementing method caching in .NET applications.
My journey from Fody to Metalama
Let’s start with a look at the Fody implementation I had been using:
public class ModuleWeaver : BaseModuleWeaver
{
// ... (more than 100 lines of complex code)
private void InjectCache(MethodDefinition method)
{
var processor = method.Body.GetILProcessor();
var instructions = method.Body.Instructions;
var returnInstructions = instructions.Where(instr => instr.OpCode == OpCodes.Ret).ToList();
foreach (var returnInstruction in returnInstructions)
{
processor.InsertInstructions(returnInstruction, SetCacheValue(method));
}
var firstInstruction = instructions.First();
processor.InsertInstructions(firstInstruction, ReturnGetValueCacheIfAny(method));
}
// ... (several complex helper methods)
}
While Fody is a powerful tool, I encountered several challenges:
- The need to write and maintain complex CIL code
- Difficult debugging processes
- Limited visibility into the generated code
- No native support for dependency injection
- A steep learning curve for team members
The Metalama advantage
When I switched to Metalama, I was able to rewrite the same caching functionality with much cleaner, more maintainable code:
public class CacheAttribute : OverrideMethodAspect
{
[IntroduceDependency]
private readonly ICache _cache;
public override dynamic? OverrideMethod()
{
var cacheKey = CacheKeyBuilder.GetCachingKey().ToValue();
if (this._cache.TryGetValue(cacheKey, out object? value))
{
return value;
}
var result = meta.Proceed();
this._cache.TryAdd(cacheKey, result);
return result;
}
}
What immediately struck me was how Metalama’s approach aligned with modern .NET development practices:
- Native C# Code: Instead of dealing with IL, I could write aspects in familiar C# syntax.
- Built-in DI Support: The
[IntroduceDependency]
attribute made dependency injection straightforward. - Clear separation of concerns: The aspect’s responsibility is clearly defined and isolated.
Simplified debugging experience
One of the most significant improvements I found was in the debugging experience. Using the LamaDebug configuration, I could inspect the generated code directly in Visual Studio:
public class Calculator
{
public int InvocationCounts { get; private set; }
[Cache]
public int Add(int a, int b)
{
var cacheKey = $"Calculator.Add((int){{{a}}}, (int){{{b}}})";
if (_cache.TryGetValue(cacheKey, out object? value))
{
return (int)value;
}
int result;
Console.WriteLine("Thinking...");
this.InvocationCounts++;
result = a + b;
_cache.TryAdd(cacheKey, result);
return result;
}
private ICache _cache;
public Calculator(ICache? cache = default)
{
this._cache = cache ?? throw new System.ArgumentNullException(nameof(cache));
}
}
This transparency in the generated code made it much easier to:
- Understand how the aspect modifies the original code
- Debug issues by stepping through the actual transformed code
- Explain the caching behavior to team members
Visual Studio integration
The Visual Studio Tools for Metalama provided another significant advantage. The CodeLens integration lets you preview aspect-modified code directly in the IDE, making it immediately clear how your aspects affect the codebase. This visibility is invaluable when working with team members who might be less familiar with AOP concepts.
Conclusion
While Fody served us well for implementing caching aspects, my experience with Metalama has shown it to be a more developer-friendly and maintainable solution. The combination of:
- Clean C# syntax for aspects
- Native dependency injection support
- Transparent debugging capabilities
- Excellent IDE integration
Makes Metalama a compelling choice for implementing caching in your .NET applications. If you’re currently using Fody for caching, I encourage you to try Metalama - you might find, as I did, that it significantly simplifies your AOP implementation while providing better tools for development and debugging.
Have a 🦙-zing day!