能否借助Roslyn实现编译时代码生成以兼容遗留DI场景?
Absolutely, this is totally achievable with Roslyn-powered compile-time code generation, and the CodeGeneration.Roslyn.BuildTime package is exactly the tool you need here. Your core idea—creating a wrapper class for IFoo<T> that delegates to a transient ISuperFoo<T> resolved via DI— is spot-on, and Roslyn can handle the compile-time code generation seamlessly, just like it does for async/await state machines.
Your plan makes perfect sense:
- Leave the legacy library untouched (since you can't modify it)
- Use
ISuperFoo<T>to mark types that need transient lifecycle - Generate compile-time wrappers that implement
IFoo<T>(so the legacy library picks them up) - Each wrapper resolves a transient
ISuperFoo<T>instance from your DI container whenExecuteis called
1. Define Your Interfaces & DI Container
First, make sure your base interfaces and container are set up correctly:
// Your existing legacy interface public interface IFoo<T> { void Execute(T obj); } // New interface for transient implementations public interface ISuperFoo<T> : IFoo<T> { } // Inheritance is optional, but simplifies alignment // Your singleton DI container (adjust based on your actual container, e.g., Autofac, Microsoft.Extensions.DependencyInjection) public class Container { public static Container Instance { get; } = new Container(); public T GetInstance<T>() { // Replace with your actual DI resolution logic return (T)Activator.CreateInstance(typeof(T)); } }
2. Set Up the Roslyn Code Generator
First, install the CodeGeneration.Roslyn.BuildTime NuGet package in your project.
Then create an incremental generator to auto-produce the FooWrapper<T> classes:
using CodeGeneration.Roslyn; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Linq; using System.Threading; using System.Threading.Tasks; [Generator] public class FooWrapperGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { // Find all non-abstract classes that implement ISuperFoo<T> var superFooImplementations = context.SyntaxProvider .CreateSyntaxProvider( predicate: (node, _) => node is ClassDeclarationSyntax classDecl && classDecl.BaseList?.Types.Any(t => t.Type.ToString().StartsWith("ISuperFoo<")) == true, transform: (ctx, _) => ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as INamedTypeSymbol) .Where(symbol => symbol != null && !symbol.IsAbstract && !symbol.IsInterface); // Generate a FooWrapper<T> for each matching ISuperFoo<T> implementation context.RegisterSourceOutput(superFooImplementations, (spc, symbol) => { var typeParam = symbol.TypeArguments[0]; var wrapperName = $"FooWrapper<{typeParam.Name}>"; var namespaceName = symbol.ContainingNamespace.ToDisplayString(); var sourceCode = $@" using System.ComponentModel; using {namespaceName}; [EditorBrowsable(EditorBrowsableState.Never)] public class {wrapperName} : IFoo<{typeParam.Name}> {{ private readonly Container _container; public {wrapperName}(Container container) {{ _container = container; }} public void Execute({typeParam.Name} obj) {{ _container.GetInstance<ISuperFoo<{typeParam.Name}>>().Execute(obj); }} }}"; spc.AddSource($"{wrapperName}.generated.cs", sourceCode); }); } }
3. Configure DI for Transient Lifecycle
Register your ISuperFoo<T> implementations as transient in your DI container. For example, with Microsoft.Extensions.DependencyInjection:
var services = new ServiceCollection(); // Register each ISuperFoo<T> as transient services.AddTransient<ISuperFoo<int>, MyIntSuperFoo>(); services.AddTransient<ISuperFoo<string>, MyStringSuperFoo>(); // Register your singleton container (if needed for wrapper injection) services.AddSingleton(Container.Instance); var serviceProvider = services.BuildServiceProvider(); // Update your Container's GetInstance to use the service provider Container.Instance.GetInstance = (type) => serviceProvider.GetService(type);
4. Let the Legacy Library Do Its Thing
The legacy library scans for all IFoo<T> implementations—including the auto-generated FooWrapper<T> classes. When it instantiates these wrappers as singletons, each Execute call will resolve a fresh transient ISuperFoo<T> instance from your DI container, exactly what you need.
- Namespace Alignment: Ensure the generated wrappers are in the same namespace as
IFoo<T>so the legacy library can find them. - Hide Wrappers: The
[EditorBrowsable(EditorBrowsableState.Never)]attribute keeps your IDE clean by hiding the auto-generated wrappers from intellisense. - Container Access: If your legacy library doesn't support constructor injection for
IFoo<T>implementations, you can fall back to using the staticContainer.Instancedirectly in the wrapper instead of constructor injection. - Performance: Using
IIncrementalGeneratorensures the generator only runs when relevant types change, keeping compile times fast.
内容的提问来源于stack exchange,提问作者vtscop

