AspectGenerator provides AOP-like method aspects for C# using compile-time call-site rewriting with source generators and C# interceptors. It lets you run code around method calls without runtime proxies, dynamic dispatch wrappers, or IL weaving.
AspectGenerator is not traditional runtime AOP. Aspects are applied to call sites visible to the current compilation.
Note
AspectGenerator itself targets netstandard2.0, but consuming projects must be built with the .NET 10 SDK/compiler because the generator uses the stable Roslyn interceptor API based on opaque InterceptableLocation data. Older SDKs and the legacy InterceptsLocation(filePath, line, character) preview API are not supported.
Important
This is not runtime AOP. Aspects are applied by rewriting call sites visible to the current compilation. Calls made through reflection, delegates, already-compiled external assemblies, or unsupported C# constructs are outside the interception model.
Install the package:
dotnet add package AspectGeneratorConfigure the consuming project:
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<InterceptorsNamespaces>$(InterceptorsNamespaces);AspectGenerator</InterceptorsNamespaces>
</PropertyGroup>Define an aspect:
using AspectGenerator;
namespace Aspects;
[Aspect(OnBeforeCall = nameof(OnBeforeCall))]
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
sealed class LogAttribute : Attribute
{
public static void OnBeforeCall(InterceptInfo info)
{
Console.WriteLine($"Calling {info.MemberInfo.Name}");
}
}Use it on a method:
using Aspects;
class Service
{
[Log]
public static void DoWork()
{
}
}
Service.DoWork();AspectGenerator finds interceptable call sites to DoWork in the current compilation and emits interceptor methods for those locations.
AspectGenerator can be configured with MSBuild properties:
<PropertyGroup>
<AspectGeneratorGenerateApi>true</AspectGeneratorGenerateApi>
<AspectGeneratorGenerateInterceptors>true</AspectGeneratorGenerateInterceptors>
<AspectGeneratorPublicApi>false</AspectGeneratorPublicApi>
<AspectGeneratorDebuggerStepThrough>false</AspectGeneratorDebuggerStepThrough>
<AspectGeneratorInterceptorsNamespace>AspectGenerator</AspectGeneratorInterceptorsNamespace>
</PropertyGroup>The same settings can be overridden with an assembly-level attribute:
using AspectGenerator;
[assembly: AspectGeneratorOptions(
GenerateApi = true,
PublicApi = false,
DebuggerStepThrough = false,
InterceptorsNamespace = "AspectGenerator")]AspectGeneratorInterceptorsNamespace must also be listed in InterceptorsNamespaces, otherwise the compiler will not enable generated interceptors from that namespace.
AspectGeneratorGenerateInterceptors defaults to false for design-time builds and true otherwise. Diagnostics still run when interceptor source emission is disabled.
AspectGenerator normally generates the authoring/runtime API into each consuming compilation:
GenerateApi=true: generateAspectAttribute,InterceptInfo,InterceptData<T>,InterceptResult, and related types.GenerateApi=false: do not generate the API. Use this when the API is supplied by an aspect library.PublicApi=false: generated API is internal to the consuming assembly.PublicApi=true: generated API is public. Treat this mode as experimental until the public contract is stabilized.
Common modes:
| Mode | Configuration | Intended use |
|---|---|---|
| Single project | GenerateApi=true, PublicApi=false |
App defines and uses its own aspects. |
| Aspect library | Library: GenerateApi=true, PublicApi=true; consumer: GenerateApi=false |
Shared aspect attributes live in a reusable assembly. |
| Cross-project app | Same interceptor namespace in each project via AspectGeneratorInterceptorsNamespace and InterceptorsNamespaces |
Intercept calls compiled in multiple projects. |
| Scenario | Status | Notes |
|---|---|---|
| Static methods | Supported | Ordinary member method calls only. |
| Instance methods | Supported | Call site must be visible to the current compilation. |
| Extension methods | Supported | Covered by unit tests. |
| Generic methods | Supported | Explicit InterceptMethods strings must match generated display names. |
Task and Task<T> async methods |
Supported | Async hooks are selected for Task targets. |
ref, out, in parameters |
Supported | Covered by unit tests. |
| Constructors | Unsupported | Platform limitation of C# interceptors. |
| Properties and indexers | Unsupported | Not ordinary method invocation syntax. |
| Operators | Unsupported | Not currently matched by the generator. |
| Delegates and reflection | Unsupported | No direct call site rewrite. |
| Local functions | Unsupported | Platform limitation. |
| Calls compiled in external assemblies | Unsupported | Rebuild the assembly containing the call site with AspectGenerator enabled. |
ValueTask and ValueTask<T> |
Not currently supported | Planned or explicitly documented as a limitation. |
AspectAttribute maps lifecycle hooks to static method names:
[Aspect(
OnInit = nameof(OnInit),
OnUsing = nameof(OnUsing),
OnUsingAsync = nameof(OnUsingAsync),
OnBeforeCall = nameof(OnBeforeCall),
OnBeforeCallAsync = nameof(OnBeforeCallAsync),
OnCall = nameof(OnCall),
OnAfterCall = nameof(OnAfterCall),
OnAfterCallAsync = nameof(OnAfterCallAsync),
OnCatch = nameof(OnCatch),
OnCatchAsync = nameof(OnCatchAsync),
OnFinally = nameof(OnFinally),
OnFinallyAsync = nameof(OnFinallyAsync))]Hook names are strings, so prefer nameof(...). Invalid names or signatures should be reported by AspectGenerator diagnostics; if a case still fails only through generated-code compiler errors, treat that as a bug.
InterceptMethods is a low-level compatibility feature that matches methods by display string:
[Aspect(
OnAfterCall = nameof(OnAfterCall),
InterceptMethods =
[
"System.String.Substring(int)",
"string.Substring(int)"
])]This form is brittle because it depends on compiler display strings, aliases, overloads, and generic spelling. Prefer method-level aspect attributes where possible.
The README is the concise entry point. The wiki should contain expanded pages with the same current terminology:
When updating docs, keep README and wiki synchronized:
- document the .NET 10 SDK/compiler requirement and use
net10.0in examples; - use
InterceptorsNamespaces, notInterceptorsPreviewNamespaces; - use
AspectGeneratorGenerateApi,AspectGeneratorPublicApi,AspectGeneratorDebuggerStepThrough, andAspectGeneratorInterceptorsNamespace; - describe
InterceptableLocationas opaque compiler data; - keep unsupported scenarios consistent between README and wiki.
Any remaining preview-era wiki page should be updated or marked with an “Obsolete preview documentation” warning.
Run the main checks locally:
dotnet restore
dotnet build --no-restore
dotnet test --no-build
dotnet pack Source/AspectGenerator.csproj --no-buildBuild artifacts under bin/ and obj/ must not be committed. If generated source snapshots are needed, store them in an explicit baseline folder under UnitTests/, not under obj/GeneratedFiles.