Skip to content

Replace reflection with source generators for AOT/trimming support #52

@StevenTCramer

Description

@StevenTCramer

Problem

TimeWarp.Mediator currently uses reflection for discovering and invoking handlers, which makes it incompatible with .NET trimming and AOT (Ahead-of-Time compilation). This results in IL2104 trim warnings when used in projects that enable trimming.

Current Impact

When building applications with trimming enabled (e.g., standalone executables), we get:

error IL2104: Assembly 'TimeWarp.Mediator' produced trim warnings. For more information see https://aka.ms/il2104

This prevents downstream projects from creating trimmed/AOT executables. For example, in TimeWarp.Amuru, we had to disable trimming for standalone CLI tools because of these warnings.

Proposed Solution

Replace the reflection-based handler discovery and invocation with source generators. Source generators can:

  1. Discover handlers at compile-time
  2. Generate strongly-typed code for handler invocation
  3. Eliminate runtime reflection entirely
  4. Enable full AOT/trimming support

Benefits

  • ✅ Full AOT compilation support
  • ✅ Trimming compatibility (smaller executables)
  • ✅ Better performance (no reflection overhead)
  • ✅ Compile-time validation of handlers
  • ✅ Better IntelliSense and debugging experience

Implementation Approach

  1. Create a source generator that:

    • Scans for classes implementing IRequestHandler/INotificationHandler
    • Generates a registry/factory class with all handlers
    • Generates invocation code without reflection
  2. Update Mediator to use the generated code instead of reflection

  3. Mark the library as trim-compatible and AOT-compatible

Example

Instead of:

// Runtime reflection to find handler
var handlerType = typeof(IRequestHandler<,>).MakeGenericType(requestType, responseType);
var handler = serviceProvider.GetService(handlerType);
var method = handlerType.GetMethod("Handle");
return await (Task)method.Invoke(handler, new[] { request, cancellationToken });

Generate:

// Generated at compile-time
return request switch
{
    GetUserQuery query => await GetService<IRequestHandler<GetUserQuery, User>>().Handle(query, cancellationToken),
    CreateUserCommand cmd => await GetService<IRequestHandler<CreateUserCommand, Result>>().Handle(cmd, cancellationToken),
    // ... other handlers
    _ => throw new HandlerNotFoundException(request.GetType())
};

References

Breaking Changes

This could potentially be implemented without breaking changes by:

  1. Keeping the same public API
  2. Using source generators internally
  3. Providing a compatibility layer for dynamic registration if needed

Priority

High - This blocks AOT adoption across all dependent projects and prevents creation of small, trimmed executables.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions