Skip to content

Interceptors

DecoWeaver uses C# 11's experimental interceptors feature to rewrite dependency injection registration calls at compile time. This page explains how interceptors work and how DecoWeaver uses them.

What are Interceptors?

Interceptors are a C# 11 feature that allows you to redirect method calls to different implementations at compile time. They work by annotating a method with [InterceptsLocation] attributes that specify which call sites to intercept.

Basic Example

// Original code
Console.WriteLine("Hello");

// Interceptor
file static class Interceptors
{
    [InterceptsLocation(version: 1, data: "base64-encoded-hash")]
    public static void WriteLine(string message)
    {
        Console.WriteLine($"[LOG] {message}");
    }
}

// At compile time, the original call is redirected to the interceptor

When compiled, the original Console.WriteLine("Hello") call is redirected to the interceptor method, which adds a [LOG] prefix.

How DecoWeaver Uses Interceptors

DecoWeaver intercepts DI registration calls to apply decorators automatically.

Without DecoWeaver

Traditional manual decoration:

services.AddScoped<IUserRepository>(sp =>
{
    var impl = new UserRepository(sp.GetRequiredService<IDbContext>());
    var cached = new CachingRepository(impl, sp.GetRequiredService<IMemoryCache>());
    var logged = new LoggingRepository(cached, sp.GetRequiredService<ILogger>());
    return logged;
});

With DecoWeaver

Automatic decoration at compile time:

[DecoratedBy<CachingRepository>]
[DecoratedBy<LoggingRepository>]
public class UserRepository : IUserRepository { }

// Your code
services.AddScoped<IUserRepository, UserRepository>();

// DecoWeaver generates an interceptor that rewrites this to:
services.AddScoped<IUserRepository>(/* factory with decorators */);

Generated Interceptor Code

When you apply [DecoratedBy] attributes, DecoWeaver generates interceptor code like this:

// Your code
services.AddScoped<IUserRepository, UserRepository>();

// Generated interceptor
file static class DecoWeaverInterceptors
{
    [InterceptsLocation(version: 1, data: "base64-encoded-hash")]
    public static IServiceCollection AddScoped_IUserRepository_UserRepository(
        this IServiceCollection services)
    {
        // Register undecorated implementation with keyed service
        services.AddKeyedScoped<IUserRepository, UserRepository>(
            "IUserRepository|UserRepository");

        // Register factory that applies decorators
        services.AddScoped<IUserRepository>(sp =>
        {
            // Get undecorated implementation
            var inner = sp.GetRequiredKeyedService<IUserRepository>(
                "IUserRepository|UserRepository");

            // Apply decorators in order
            inner = new CachingRepository(
                inner,
                sp.GetRequiredService<IMemoryCache>());

            inner = new LoggingRepository(
                inner,
                sp.GetRequiredService<ILogger<LoggingRepository>>());

            return inner;
        });

        return services;
    }
}

Key Components

  1. [InterceptsLocation]: Tells the compiler which call site to intercept
  2. Keyed Service: Registers the undecorated implementation
  3. Factory Registration: Wraps the implementation with decorators
  4. File-Scoped Class: Uses file keyword to prevent naming collisions

InterceptsLocation Attribute

The [InterceptsLocation] attribute identifies which call sites to intercept:

[InterceptsLocation(version: 1, data: "base64-encoded-hash")]

Parameters: - version: Always 1 (format version) - data: A base64-encoded hash that uniquely identifies the location of the code to intercept

The data parameter contains an encoded hash value that points to the specific call site in your source code. This hash is calculated by the source generator based on the file path and position of the method call.

Multiple Locations

A single interceptor method can intercept multiple call sites:

[InterceptsLocation(version: 1, data: "hash1")]
[InterceptsLocation(version: 1, data: "hash2")]
[InterceptsLocation(version: 1, data: "hash3")]
public static IServiceCollection AddScoped_IUserRepository_UserRepository(
    this IServiceCollection services)
{
    // Implementation
}

This allows DecoWeaver to intercept all registrations of UserRepository across your codebase.

File-Scoped Types

DecoWeaver uses file-scoped types (file keyword) to prevent naming collisions:

file static class DecoWeaverInterceptors
{
    // This class is only visible within this file
}

This prevents conflicts when generating interceptors in multiple files.

Viewing Generated Code

Visual Studio

  1. Build your project
  2. Solution Explorer → Show All Files
  3. Navigate to obj/Debug/net8.0/generated/DecoWeaver.Generators/
  4. Open DecoWeaver.Interceptors.g.cs

Rider

  1. Build your project
  2. Solution Explorer → Show Generated Files
  3. Expand the DecoWeaver.Generators node
  4. Open DecoWeaver.Interceptors.g.cs

Visual Studio Code

  1. Build your project
  2. Navigate to obj/Debug/net8.0/generated/DecoWeaver.Generators/
  3. Open DecoWeaver.Interceptors.g.cs

Interceptor Requirements

For interceptors to work correctly:

C# Language Version

Interceptors require C# 11 or later:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <LangVersion>11</LangVersion> <!-- or "latest" -->
  </PropertyGroup>
</Project>

The DecoWeaver package automatically enables the interceptors feature, so you don't need to manually configure any experimental feature flags.

Method Signature Match

Interceptor methods must exactly match the signature of the method they intercept:

// Original method
public static IServiceCollection AddScoped<TService, TImplementation>(
    this IServiceCollection services)
    where TService : class
    where TImplementation : class, TService

// Interceptor must match exactly
[InterceptsLocation(...)]
public static IServiceCollection AddScoped<TService, TImplementation>(
    this IServiceCollection services)
    where TService : class
    where TImplementation : class, TService
{
    // Implementation
}

Generic Type Decoration Interceptors

For closed generic registrations with open generic decorators, DecoWeaver generates interceptors that close decorator types at runtime:

Registration Requirement

DecoWeaver requires closed generic registrations using the AddScoped<TService, TImplementation>() syntax. Open generic registrations using AddScoped(typeof(...), typeof(...)) are NOT intercepted.

// Your code - closed generic registration with open generic decorator
[DecoratedBy<CachingRepository<>>]  // Open generic decorator
public class Repository<T> : IRepository<T> where T : class { }

services.AddScoped<IRepository<User>, Repository<User>>();  // Closed registration

// Generated interceptor for this specific closed registration
[InterceptsLocation(version: 1, data: "base64-encoded-hash")]
public static IServiceCollection AddScoped_IRepository_User_Repository_User(
    this IServiceCollection services)
{
    // Register undecorated implementation with keyed service
    services.AddKeyedScoped<IRepository<User>, Repository<User>>(
        "IRepository<User>|Repository<User>");

    // Register factory that closes the open generic decorator
    services.AddScoped<IRepository<User>>(sp =>
    {
        // Get undecorated implementation
        var inner = sp.GetRequiredKeyedService<IRepository<User>>(
            "IRepository<User>|Repository<User>");

        // Close open generic decorator at runtime: CachingRepository<> → CachingRepository<User>
        var decoratorType = typeof(CachingRepository<>).MakeGenericType(typeof(User));

        // Resolve dependencies and construct
        var cache = sp.GetRequiredService<IMemoryCache>();
        return (IRepository<User>)Activator.CreateInstance(decoratorType, inner, cache);
    });

    return services;
}

Each closed generic registration gets its own interceptor with the decorator closed to the appropriate type.

Debugging Interceptors

Compile-Time Errors

If an interceptor fails to apply, you'll see compile errors:

CS9137: The 'interceptors' preview feature is not enabled.

Solution: Enable C# 11 and interceptors in your project file.

CS9144: Cannot intercept method 'AddScoped' because it is not interceptable.

Solution: Ensure you're using Microsoft.Extensions.DependencyInjection 8.0+.

Runtime Errors

If decorators aren't being applied:

  1. Check that generated files exist in obj/Debug/generated/
  2. Verify all decorator dependencies are registered
  3. Ensure keyed services are supported (.NET 8+)
  4. Rebuild the solution to regenerate interceptors

Viewing Interceptor Locations

Add logging to see which interceptors are generated:

// This is handled by DecoWeaver's source generator
// Check build output for diagnostic messages

Performance

Interceptors have zero runtime overhead:

  • No reflection at runtime
  • No dynamic code generation
  • No assembly scanning
  • Direct method calls in generated code

The only cost is during compilation when interceptors are generated.

Limitations

Cannot Intercept Non-Public Methods

Interceptors only work with public methods:

// ✅ Can intercept
public static IServiceCollection AddScoped<T, TImpl>(this IServiceCollection services)

// ❌ Cannot intercept
internal static IServiceCollection AddScoped<T, TImpl>(this IServiceCollection services)

Cannot Intercept Virtual Methods

Virtual methods cannot be intercepted:

// ✅ Can intercept
public static IServiceCollection AddScoped(...)

// ❌ Cannot intercept
public virtual IServiceCollection AddScoped(...)

Cannot Intercept Generic Methods with Constraints

Some complex generic constraints may not work:

// ✅ Works
public static void Method<T>() where T : class

// ⚠️ May not work
public static void Method<T>() where T : class, IDisposable, new()

Experimental Feature Status

Interceptors are currently experimental in C# 11:

  • API may change in future versions
  • Some IDEs may show warnings
  • Feature may be refined or stabilized in C# 12+

DecoWeaver uses interceptors safely within their documented capabilities. The feature is stable enough for production use with .NET 8+.

Future of Interceptors

Microsoft is actively working on interceptors:

  • Potential stabilization in future C# versions
  • Improved IDE support
  • Better debugging experience
  • Additional use cases beyond DI

DecoWeaver will continue to support and leverage interceptors as the feature evolves.

Next Steps