To create a code generator that automatically caches any function with a cache attribute that takes a duration, you can use a source generator in .NET. Source generators allow you to generate additional source code at compile time.
Step 1: Define the Cache Attribute
First, define the cache attribute that will be used to mark methods for caching:
using System;
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class CacheAttribute : Attribute
{
public CacheAttribute(int durationInSeconds)
{
DurationInSeconds = durationInSeconds;
}
public int DurationInSeconds { get; }
}
Step 2: Create the Source Generator
Next, create the source generator that will generate the caching logic for methods marked with the CacheAttribute
.
Create a new class library project for the source generator.
Add the necessary NuGet packages:
Microsoft.CodeAnalysis.CSharp
,Microsoft.CodeAnalysis.Analyzers
, andMicrosoft.CodeAnalysis.CSharp.Workspaces
.Implement the source generator:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Generic;
using System.Linq;
using System.Text;
[Generator]
public class CacheSourceGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
// Register a syntax receiver that will be created for each generation pass
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
// Retrieve the populated receiver
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
return;
// Get the cache attribute symbol
var cacheAttributeSymbol = context.Compilation.GetTypeByMetadataName("CacheAttribute");
foreach (var method in receiver.CandidateMethods)
{
var model = context.Compilation.GetSemanticModel(method.SyntaxTree);
var methodSymbol = model.GetDeclaredSymbol(method) as IMethodSymbol;
if (methodSymbol == null)
continue;
// Check if the method has the CacheAttribute
var cacheAttribute = methodSymbol.GetAttributes().FirstOrDefault(ad => ad.AttributeClass.Equals(cacheAttributeSymbol, SymbolEqualityComparer.Default));
if (cacheAttribute == null)
continue;
// Get the duration from the attribute
var duration = (int)cacheAttribute.ConstructorArguments[0].Value;
// Generate the caching logic
var source = GenerateCachingLogic(methodSymbol, duration);
context.AddSource($"{methodSymbol.Name}_Cache.cs", SourceText.From(source, Encoding.UTF8));
}
}
private string GenerateCachingLogic(IMethodSymbol methodSymbol, int duration)
{
var namespaceName = methodSymbol.ContainingNamespace.ToDisplayString();
var className = methodSymbol.ContainingType.Name;
var methodName = methodSymbol.Name;
var returnType = methodSymbol.ReturnType.ToDisplayString();
var parameters = string.Join(", ", methodSymbol.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}"));
var arguments = string.Join(", ", methodSymbol.Parameters.Select(p => p.Name));
return $@"
using System;
using System.Threading.Tasks;
using ZiggyCreatures.Caching.Fusion;
namespace {namespaceName}
{{
public partial class {className}
{{
private readonly IFusionCache _cache;
public {className}(IFusionCache cache)
{{
_cache = cache;
}}
public async Task<{returnType}> {methodName}_WithCache({parameters})
{{
var cacheKey = $""{methodName}_{{string.Join('_', new object[] {{ {arguments} }})}}"";
return await _cache.GetOrSetAsync(
cacheKey,
async () => await {methodName}({arguments}),
TimeSpan.FromSeconds({duration})
);
}}
}}
}}
";
}
private class SyntaxReceiver : ISyntaxReceiver
{
public List<MethodDeclarationSyntax> CandidateMethods { get; } = new List<MethodDeclarationSyntax>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
// Find all method declarations with the CacheAttribute
if (syntaxNode is MethodDeclarationSyntax methodDeclarationSyntax &&
methodDeclarationSyntax.AttributeLists.Count > 0)
{
CandidateMethods.Add(methodDeclarationSyntax);
}
}
}
}
Step 3: Use the Source Generator
- Add the source generator project as a reference to your main project.
- Mark methods with the
CacheAttribute
to enable caching.
Example Usage:
public class BookService
{
[Cache(60)]
public async Task<Book> GetBookAsync(int bookId)
{
// Method implementation
}
}
The source generator will automatically generate a method with caching logic for each method marked with the CacheAttribute
.
Summary
This approach uses a source generator to automatically generate caching logic for methods marked with a CacheAttribute
. The generated code uses FusionCache
to handle caching, and the duration is specified in the attribute. This ensures that caching is applied consistently and automatically to the specified methods.