Skip to content

数据流转换

深入理解 IncrementalValueProvider 和 IncrementalValuesProvider 的数据流转换机制

📋 文档信息

属性
难度中级
阅读时间25 分钟
前置知识管道操作基础、LINQ
相关文档管道操作详解

🎯 学习目标

完成本文档后,你将能够:

  • ✅ 理解 IncrementalValueProvider 和 IncrementalValuesProvider 的区别
  • ✅ 掌握单值和多值 Provider 的转换
  • ✅ 理解数据流的类型系统
  • ✅ 应用正确的 Provider 类型

📚 快速导航

章节说明
IncrementalValueProvider单个值的 Provider
IncrementalValuesProvider多个值的 Provider
类型转换Provider 之间的转换
实战示例真实使用场景

IncrementalValueProvider

IncrementalValueProvider<T> 表示单个值的 Provider。它用于表示全局配置、编译选项等单一数据源。

基本概念

csharp
using Microsoft.CodeAnalysis;

/// <summary>
/// IncrementalValueProvider 基本示例
/// </summary>
public class ValueProviderBasics
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // CompilationProvider 是 IncrementalValueProvider<Compilation>
        IncrementalValueProvider<Compilation> compilation = 
            context.CompilationProvider;
        
        // 转换为其他单个值
        IncrementalValueProvider<string> assemblyName = compilation
            .Select((comp, _) => comp.AssemblyName);
        
        IncrementalValueProvider<OutputKind> outputKind = compilation
            .Select((comp, _) => comp.Options.OutputKind);
        
        // 使用单个值
        context.RegisterSourceOutput(assemblyName, (spc, name) =>
        {
            var code = $@"
// Assembly: {name}
namespace Generated
{{
    public static class AssemblyInfo
    {{
        public const string Name = ""{name}"";
    }}
}}";
            spc.AddSource("AssemblyInfo.g.cs", code);
        });
    }
}

创建 IncrementalValueProvider

csharp
using Microsoft.CodeAnalysis;

/// <summary>
/// 创建 IncrementalValueProvider 的各种方式
/// </summary>
public class CreateValueProvider
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 方式 1: 从 CompilationProvider
        IncrementalValueProvider<Compilation> compilation = 
            context.CompilationProvider;
        
        // 方式 2: 从 Collect 操作
        var classes = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => ((ClassDeclarationSyntax)ctx.Node).Identifier.Text);
        
        IncrementalValueProvider<ImmutableArray<string>> allClasses = 
            classes.Collect();  // 多个值 -> 单个数组
        
        // 方式 3: 从 Select 操作
        IncrementalValueProvider<int> classCount = allClasses
            .Select((classes, _) => classes.Length);
        
        // 方式 4: 从 Combine 操作
        IncrementalValueProvider<(Compilation, ImmutableArray<string>)> combined = 
            compilation.Combine(allClasses);
    }
}

IncrementalValueProvider 操作

csharp
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;

/// <summary>
/// IncrementalValueProvider 支持的操作
/// </summary>
public class ValueProviderOperations
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var compilation = context.CompilationProvider;
        
        // Select: 转换值
        var assemblyName = compilation
            .Select((comp, _) => comp.AssemblyName);
        
        // Combine: 组合两个 ValueProvider
        var additionalFiles = context.AdditionalTextsProvider.Collect();
        var combined = compilation.Combine(additionalFiles);
        
        // WithComparer: 使用自定义比较器
        var customCompared = compilation
            .Select((comp, _) => new CompilationInfo(comp.AssemblyName))
            .WithComparer(new CompilationInfoComparer());
        
        // WithTrackingName: 添加跟踪名称
        var tracked = compilation
            .Select((comp, _) => comp.AssemblyName)
            .WithTrackingName("ExtractAssemblyName");
    }
    
    private record CompilationInfo(string AssemblyName);
    
    private class CompilationInfoComparer : IEqualityComparer<CompilationInfo>
    {
        public bool Equals(CompilationInfo x, CompilationInfo y)
        {
            return x?.AssemblyName == y?.AssemblyName;
        }
        
        public int GetHashCode(CompilationInfo obj)
        {
            return obj?.AssemblyName?.GetHashCode() ?? 0;
        }
    }
}

IncrementalValuesProvider

IncrementalValuesProvider<T> 表示多个值的 Provider。它用于表示类集合、方法集合等多个数据项。

基本概念

csharp
using Microsoft.CodeAnalysis;

/// <summary>
/// IncrementalValuesProvider 基本示例
/// </summary>
public class ValuesProviderBasics
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // CreateSyntaxProvider 返回 IncrementalValuesProvider
        IncrementalValuesProvider<ClassDeclarationSyntax> classes = 
            context.SyntaxProvider.CreateSyntaxProvider(
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node);
        
        // 转换为其他多个值
        IncrementalValuesProvider<string> classNames = classes
            .Select((classDecl, _) => classDecl.Identifier.Text);
        
        // 为每个类生成代码
        context.RegisterSourceOutput(classNames, (spc, className) =>
        {
            var code = $@"
// Generated for class: {className}
namespace Generated
{{
    public static class {className}Extensions
    {{
        public static string GetClassName() => ""{className}"";
    }}
}}";
            spc.AddSource($"{className}.Extensions.g.cs", code);
        });
    }
}

创建 IncrementalValuesProvider

csharp
using Microsoft.CodeAnalysis;

/// <summary>
/// 创建 IncrementalValuesProvider 的各种方式
/// </summary>
public class CreateValuesProvider
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 方式 1: 从 CreateSyntaxProvider
        IncrementalValuesProvider<ClassDeclarationSyntax> classes = 
            context.SyntaxProvider.CreateSyntaxProvider(
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node);
        
        // 方式 2: 从 ForAttributeWithMetadataName
        IncrementalValuesProvider<INamedTypeSymbol> attributedClasses = 
            context.SyntaxProvider.ForAttributeWithMetadataName(
                "MyAttribute",
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => (INamedTypeSymbol)ctx.TargetSymbol);
        
        // 方式 3: 从 SelectMany
        IncrementalValuesProvider<MethodDeclarationSyntax> methods = classes
            .SelectMany((classDecl, _) =>
            {
                return classDecl.Members
                    .OfType<MethodDeclarationSyntax>()
                    .ToImmutableArray();
            });
        
        // 方式 4: 从 AdditionalTextsProvider
        IncrementalValuesProvider<AdditionalText> additionalFiles = 
            context.AdditionalTextsProvider;
    }
}

IncrementalValuesProvider 操作

csharp
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;

/// <summary>
/// IncrementalValuesProvider 支持的操作
/// </summary>
public class ValuesProviderOperations
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classes = context.SyntaxProvider.CreateSyntaxProvider(
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node);
        
        // Select: 转换每个值
        var classNames = classes
            .Select((classDecl, _) => classDecl.Identifier.Text);
        
        // Where: 过滤值
        var publicClasses = classes
            .Where((classDecl, _) =>
                classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));
        
        // SelectMany: 展开为多个值
        var methods = classes
            .SelectMany((classDecl, _) =>
                classDecl.Members
                    .OfType<MethodDeclarationSyntax>()
                    .ToImmutableArray());
        
        // Collect: 收集到单个数组
        IncrementalValueProvider<ImmutableArray<string>> allClassNames = 
            classNames.Collect();
        
        // Combine: 组合单个值
        var compilation = context.CompilationProvider;
        var combined = classes.Combine(compilation);
        
        // WithComparer: 使用自定义比较器
        var customCompared = classNames
            .WithComparer(StringComparer.OrdinalIgnoreCase);
        
        // WithTrackingName: 添加跟踪名称
        var tracked = classes
            .WithTrackingName("FindClasses");
    }
}

类型转换

ValuesProvider 转 ValueProvider

使用 Collect 操作将多个值收集到一个数组:

csharp
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;

/// <summary>
/// 将 ValuesProvider 转换为 ValueProvider
/// </summary>
public class ValuesToValueConversion
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 多个类名
        IncrementalValuesProvider<string> classNames = 
            context.SyntaxProvider
                .CreateSyntaxProvider(
                    predicate: (node, _) => node is ClassDeclarationSyntax,
                    transform: (ctx, _) => 
                        ((ClassDeclarationSyntax)ctx.Node).Identifier.Text);
        
        // 收集到单个数组
        IncrementalValueProvider<ImmutableArray<string>> allClassNames = 
            classNames.Collect();
        
        // 现在可以作为单个值使用
        context.RegisterSourceOutput(allClassNames, (spc, names) =>
        {
            // names 是 ImmutableArray<string>
            var code = $@"
// Found {names.Length} classes
namespace Generated
{{
    public static class ClassRegistry
    {{
        public static readonly string[] AllClasses = new[]
        {{
            {string.Join(",\n            ", names.Select(n => $"\"{n}\""))}
        }};
    }}
}}";
            spc.AddSource("ClassRegistry.g.cs", code);
        });
    }
}

ValueProvider 转 ValuesProvider

使用 SelectMany 操作将单个值展开为多个值:

csharp
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;

/// <summary>
/// 将 ValueProvider 转换为 ValuesProvider
/// </summary>
public class ValueToValuesConversion
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 单个编译对象
        IncrementalValueProvider<Compilation> compilation = 
            context.CompilationProvider;
        
        // 展开为多个类型符号
        IncrementalValuesProvider<INamedTypeSymbol> allTypes = compilation
            .SelectMany((comp, _) =>
            {
                // 获取所有类型
                return GetAllTypes(comp.GlobalNamespace);
            });
        
        // 现在可以为每个类型生成代码
        context.RegisterSourceOutput(allTypes, (spc, typeSymbol) =>
        {
            var code = $"// Type: {typeSymbol.Name}";
            spc.AddSource($"{typeSymbol.Name}.Info.g.cs", code);
        });
    }
    
    private ImmutableArray<INamedTypeSymbol> GetAllTypes(INamespaceSymbol ns)
    {
        var types = ImmutableArray.CreateBuilder<INamedTypeSymbol>();
        
        // 获取当前命名空间的类型
        foreach (var member in ns.GetMembers())
        {
            if (member is INamedTypeSymbol type)
            {
                types.Add(type);
            }
            else if (member is INamespaceSymbol childNs)
            {
                // 递归获取子命名空间的类型
                types.AddRange(GetAllTypes(childNs));
            }
        }
        
        return types.ToImmutable();
    }
}

组合不同类型的 Provider

使用 Combine 操作组合 ValueProvider 和 ValuesProvider:

csharp
using Microsoft.CodeAnalysis;

/// <summary>
/// 组合不同类型的 Provider
/// </summary>
public class CombineProviders
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // ValueProvider: 单个编译对象
        var compilation = context.CompilationProvider;
        
        // ValuesProvider: 多个类
        var classes = context.SyntaxProvider.CreateSyntaxProvider(
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node);
        
        // 组合: 每个类都会与编译对象组合
        var combined = classes.Combine(compilation);
        
        // 类型: IncrementalValuesProvider<(ClassDeclarationSyntax, Compilation)>
        context.RegisterSourceOutput(combined, (spc, data) =>
        {
            var (classDecl, comp) = data;
            
            var code = $@"
// Class: {classDecl.Identifier.Text}
// Assembly: {comp.AssemblyName}
";
            spc.AddSource($"{classDecl.Identifier.Text}.Info.g.cs", code);
        });
    }
}

实战示例

示例 1: 全局配置 + 多个类

csharp
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;

/// <summary>
/// 使用全局配置生成多个类的代码
/// </summary>
[Generator]
public class GlobalConfigGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // ValueProvider: 全局配置
        var globalConfig = context.CompilationProvider
            .Select((comp, _) => new GlobalConfig(
                AssemblyName: comp.AssemblyName,
                IsDebug: comp.Options.OptimizationLevel == OptimizationLevel.Debug,
                TargetFramework: comp.Options.OutputKind.ToString()));
        
        // ValuesProvider: 多个类
        var classes = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "MyNamespace.GenerateAttribute",
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => new ClassInfo(
                    ((INamedTypeSymbol)ctx.TargetSymbol).Name,
                    ((INamedTypeSymbol)ctx.TargetSymbol).ContainingNamespace.ToDisplayString()));
        
        // 组合: 每个类都使用全局配置
        var combined = classes.Combine(globalConfig);
        
        // 为每个类生成代码
        context.RegisterSourceOutput(combined, (spc, data) =>
        {
            var (classInfo, config) = data;
            
            var code = $@"
// Generated for: {classInfo.Name}
// Assembly: {config.AssemblyName}
// Debug: {config.IsDebug}
// Target: {config.TargetFramework}

namespace {classInfo.Namespace}
{{
    partial class {classInfo.Name}
    {{
        public static class GeneratedInfo
        {{
            public const string AssemblyName = ""{config.AssemblyName}"";
            public const bool IsDebug = {config.IsDebug.ToString().ToLower()};
            public const string TargetFramework = ""{config.TargetFramework}"";
        }}
    }}
}}";
            spc.AddSource($"{classInfo.Name}.GeneratedInfo.g.cs", code);
        });
    }
    
    private record GlobalConfig(
        string AssemblyName,
        bool IsDebug,
        string TargetFramework);
    
    private record ClassInfo(
        string Name,
        string Namespace);
}

示例 2: 收集所有类生成注册表

csharp
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;

/// <summary>
/// 收集所有类生成统一的注册表
/// </summary>
[Generator]
public class RegistryGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // ValuesProvider: 多个类信息
        var classes = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "MyNamespace.RegisterAttribute",
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) =>
                {
                    var symbol = (INamedTypeSymbol)ctx.TargetSymbol;
                    var attribute = ctx.Attributes[0];
                    
                    // 获取注册名称
                    var registrationName = symbol.Name;
                    if (attribute.ConstructorArguments.Length > 0)
                    {
                        registrationName = attribute.ConstructorArguments[0].Value?.ToString() 
                            ?? symbol.Name;
                    }
                    
                    return new RegistrationInfo(
                        symbol.ToDisplayString(),
                        registrationName,
                        symbol.ContainingNamespace.ToDisplayString());
                });
        
        // ValueProvider: 收集所有类到一个数组
        var allClasses = classes.Collect();
        
        // 生成统一的注册表
        context.RegisterSourceOutput(allClasses, (spc, registrations) =>
        {
            var code = GenerateRegistry(registrations);
            spc.AddSource("ServiceRegistry.g.cs", code);
        });
    }
    
    private string GenerateRegistry(ImmutableArray<RegistrationInfo> registrations)
    {
        // 按命名空间分组
        var grouped = registrations
            .GroupBy(r => r.Namespace)
            .OrderBy(g => g.Key);
        
        var registrationCode = new System.Text.StringBuilder();
        
        foreach (var group in grouped)
        {
            registrationCode.AppendLine($"        // {group.Key}");
            
            foreach (var reg in group.OrderBy(r => r.RegistrationName))
            {
                registrationCode.AppendLine(
                    $"        Register<{reg.FullTypeName}>(\"{reg.RegistrationName}\");");
            }
            
            registrationCode.AppendLine();
        }
        
        return $@"
using System;
using System.Collections.Generic;

namespace Generated
{{
    /// <summary>
    /// 自动生成的服务注册表
    /// 包含 {registrations.Length} 个注册项
    /// </summary>
    public static class ServiceRegistry
    {{
        private static readonly Dictionary<string, Type> _services = 
            new Dictionary<string, Type>();
        
        static ServiceRegistry()
        {{
            InitializeRegistrations();
        }}
        
        private static void InitializeRegistrations()
        {{
{registrationCode}        }}
        
        private static void Register<T>(string name)
        {{
            _services[name] = typeof(T);
        }}
        
        public static Type GetServiceType(string name)
        {{
            return _services.TryGetValue(name, out var type) ? type : null;
        }}
        
        public static IEnumerable<string> GetAllServiceNames()
        {{
            return _services.Keys;
        }}
        
        public static int Count => _services.Count;
    }}
}}";
    }
    
    private record RegistrationInfo(
        string FullTypeName,
        string RegistrationName,
        string Namespace);
}

示例 3: 展开编译中的所有类型

csharp
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;

/// <summary>
/// 从编译对象展开所有类型
/// </summary>
[Generator]
public class AllTypesGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // ValueProvider: 单个编译对象
        var compilation = context.CompilationProvider;
        
        // 展开为 ValuesProvider: 多个类型
        var allTypes = compilation
            .SelectMany((comp, _) =>
            {
                // 获取所有公共类型
                return GetAllPublicTypes(comp.GlobalNamespace);
            })
            .WithTrackingName("ExtractAllTypes");
        
        // 过滤: 只保留类
        var classes = allTypes
            .Where((type, _) => type.TypeKind == TypeKind.Class)
            .WithTrackingName("FilterClasses");
        
        // 收集所有类名
        var classNames = classes
            .Select((type, _) => type.ToDisplayString())
            .Collect();
        
        // 生成类型目录
        context.RegisterSourceOutput(classNames, (spc, names) =>
        {
            var code = GenerateTypeCatalog(names);
            spc.AddSource("TypeCatalog.g.cs", code);
        });
    }
    
    private ImmutableArray<INamedTypeSymbol> GetAllPublicTypes(INamespaceSymbol ns)
    {
        var types = ImmutableArray.CreateBuilder<INamedTypeSymbol>();
        
        foreach (var member in ns.GetMembers())
        {
            if (member is INamedTypeSymbol type && 
                type.DeclaredAccessibility == Accessibility.Public)
            {
                types.Add(type);
            }
            else if (member is INamespaceSymbol childNs)
            {
                types.AddRange(GetAllPublicTypes(childNs));
            }
        }
        
        return types.ToImmutable();
    }
    
    private string GenerateTypeCatalog(ImmutableArray<string> typeNames)
    {
        var typeList = string.Join(",\n            ", 
            typeNames.Select(n => $"\"{n}\""));
        
        return $@"
namespace Generated
{{
    /// <summary>
    /// 所有公共类型的目录
    /// 包含 {typeNames.Length} 个类型
    /// </summary>
    public static class TypeCatalog
    {{
        public static readonly string[] AllTypes = new[]
        {{
            {typeList}
        }};
        
        public static int Count => {typeNames.Length};
    }}
}}";
    }
}

🔑 关键要点

ValueProvider vs ValuesProvider

特性IncrementalValueProviderIncrementalValuesProvider
表示单个值多个值
示例编译对象、全局配置类集合、方法集合
转换SelectSelect, Where, SelectMany
收集-Collect → ValueProvider
展开SelectMany → ValuesProvider-
组合CombineCombine (与 ValueProvider)

类型转换规则

csharp
// ValuesProvider → ValueProvider
IncrementalValuesProvider<T> values;
IncrementalValueProvider<ImmutableArray<T>> collected = values.Collect();

// ValueProvider → ValuesProvider
IncrementalValueProvider<T> value;
IncrementalValuesProvider<U> expanded = value.SelectMany((v, _) => GetMany(v));

// ValuesProvider + ValueProvider → ValuesProvider
IncrementalValuesProvider<T> values;
IncrementalValueProvider<U> value;
IncrementalValuesProvider<(T, U)> combined = values.Combine(value);

最佳实践

  1. 使用正确的 Provider 类型

    • 全局数据 → ValueProvider
    • 集合数据 → ValuesProvider
  2. 避免不必要的 Collect

    • Collect 会等待所有值,影响性能
    • 只在真正需要所有数据时使用
  3. 合理使用 Combine

    • 组合全局配置和局部数据
    • 每个局部数据都会与全局数据组合
  4. 添加 WithTrackingName

    • 便于性能分析和调试
    • 识别数据流瓶颈

🔗 相关资源


🚀 下一步


最后更新: 2026-02-05

基于 MIT 许可发布