数据流转换
深入理解 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
| 特性 | IncrementalValueProvider | IncrementalValuesProvider |
|---|---|---|
| 表示 | 单个值 | 多个值 |
| 示例 | 编译对象、全局配置 | 类集合、方法集合 |
| 转换 | Select | Select, Where, SelectMany |
| 收集 | - | Collect → ValueProvider |
| 展开 | SelectMany → ValuesProvider | - |
| 组合 | Combine | Combine (与 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);最佳实践
使用正确的 Provider 类型
- 全局数据 → ValueProvider
- 集合数据 → ValuesProvider
避免不必要的 Collect
- Collect 会等待所有值,影响性能
- 只在真正需要所有数据时使用
合理使用 Combine
- 组合全局配置和局部数据
- 每个局部数据都会与全局数据组合
添加 WithTrackingName
- 便于性能分析和调试
- 识别数据流瓶颈
🔗 相关资源
🚀 下一步
最后更新: 2026-02-05