You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Incremental Source Generator中获取泛型类的泛型实参

Extract Generic Arguments from User<T> Instantiations Using Incremental Source Generator

Here's a complete implementation to capture the generic type arguments whenever your User<T> class is instantiated, using an incremental source generator:

Step 1: Implement the Generator

Create a class that implements IIncrementalGenerator, combining syntax filtering and semantic analysis to target User<T> instantiations:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

[Generator]
public class UserGenericArgumentGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // Capture all User<T> instantiations (explicit and target-typed)
        var userInstances = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => IsUserInstantiation(node),
                transform: (ctx, _) => GetGenericArgumentDetails(ctx))
            .Where(details => details != null);

        // Process the captured details (example: emit diagnostics or generate code)
        context.RegisterSourceOutput(userInstances, (context, details) =>
        {
            // Emit an info diagnostic showing the generic argument
            var diagnostic = Diagnostic.Create(
                new DiagnosticDescriptor(
                    "USERGEN001",
                    "User<T> Instantiation Detected",
                    "User created with generic type: {0}",
                    "UserGenerator",
                    DiagnosticSeverity.Info,
                    isEnabledByDefault: true),
                details.Location,
                details.GenericArgumentName);

            context.ReportDiagnostic(diagnostic);

            // Uncomment below to generate source code using the generic argument
            // context.AddSource(
            //     $"UserArg_{details.Location.GetHashCode()}.cs",
            //     GenerateTrackingCode(details.GenericArgumentName));
        });
    }

    // Check if the syntax node is a User<T> instantiation
    private static bool IsUserInstantiation(SyntaxNode node)
    {
        if (node is not ObjectCreationExpressionSyntax objExpr) return false;

        // Handle explicit type (new User<bool>()) or target-typed (new())
        var typeNode = objExpr.Type ?? 
                       objExpr.Parent?
                              .DescendantNodes()
                              .OfType<VariableDeclaratorSyntax>()
                              .FirstOrDefault()?
                              .Type;

        return typeNode is GenericNameSyntax genericName &&
               genericName.Identifier.Text == "User" &&
               genericName.TypeArgumentList.Arguments.Count == 1;
    }

    // Get semantic details about the generic argument
    private static GenericArgumentDetails? GetGenericArgumentDetails(GeneratorSyntaxContext ctx)
    {
        var objExpr = (ObjectCreationExpressionSyntax)ctx.Node;
        var typeNode = objExpr.Type ?? 
                       objExpr.Parent?
                              .DescendantNodes()
                              .OfType<VariableDeclaratorSyntax>()
                              .FirstOrDefault()?
                              .Type;

        if (typeNode is not GenericNameSyntax genericName) return null;

        var semanticModel = ctx.SemanticModel;
        var typeSymbol = semanticModel.GetTypeInfo(genericName).Type as INamedTypeSymbol;

        // Verify it's actually our User<T> class
        if (typeSymbol == null || !typeSymbol.ConstructedFrom.Name.Equals("User")) return null;

        var genericArg = typeSymbol.TypeArguments[0];
        return new GenericArgumentDetails(
            genericArg.ToDisplayString(),
            objExpr.GetLocation());
    }

    private record GenericArgumentDetails(string GenericArgumentName, Location Location);

    // Optional: Generate code that tracks the generic argument
    private static string GenerateTrackingCode(string argName)
    {
        return $$"""
        namespace UserGeneratorOutput;

        public static class UserGenericArgTracker_{{argName}}
        {
            public const string TrackedGenericType = "{{argName}}";
        }
        """;
    }
}

How It Works

  1. Syntax Filtering: The predicate checks for ObjectCreationExpressionSyntax nodes that correspond to User<T>—this includes both explicit instantiations (new User<bool>() ) and target-typed ones (new() where the variable type is User<SomeOtherClass>).
  2. Semantic Analysis: We use the semantic model to resolve the actual type symbol of the generic User class, ensuring we're targeting the correct type (not a different class named User). Then we extract the first generic argument.
  3. Processing: In this example, we emit an info-level diagnostic to show the generic type, but you can modify the RegisterSourceOutput action to generate source code, collect statistics, or perform other tasks.

Testing

When you build your project with this generator enabled, every User<T> instantiation will trigger a diagnostic. For example:

  • User<bool> userWithBool = new(); → Diagnostic: "User created with generic type: bool"
  • User<SomeOtherClass> userWithSomeOtherClass = new(); → Diagnostic: "User created with generic type: SomeOtherClass"

Setup Notes

  • Ensure your generator project references Microsoft.CodeAnalysis.CSharp and Microsoft.CodeAnalysis.Analyzers NuGet packages.
  • If you need to handle additional contexts (like User<T> being used as a method return type or property type), expand the syntax predicate to include those node types.

内容的提问来源于stack exchange,提问作者İsmail Furkan GÖKHASAN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 07:04:58