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

在C#源代码生成器中创建非C#文件的高效方案

生成非C#文件的最优高效方案

1. 通过MSBuild属性传递项目路径(推荐)

这是最可靠且优雅的方式,直接从MSBuild获取项目级路径,避免依赖语法树文件路径的不确定性:

步骤:

  • 在用户项目文件(.csproj)中配置输出目录属性:
    <PropertyGroup>
      <!-- 自定义TS文件输出目录,可根据需求修改 -->
      <TsOutputDir>$(ProjectDir)GeneratedTsFiles</TsOutputDir>
    </PropertyGroup>
    
  • 在生成器中读取该属性:
    可以在Initialize方法中缓存路径,避免重复获取:
    [Generator]
    public class TsGenerator : ISourceGenerator
    {
        private string? _tsOutputDir;
    
        public void Initialize(GeneratorInitializationContext context)
        {
            // 从MSBuild全局配置中读取自定义属性
            if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(
                "build_property.TsOutputDir", out var outputDir))
            {
                _tsOutputDir = outputDir;
                // 确保目录存在,CreateDirectory存在时不会报错
                Directory.CreateDirectory(_tsOutputDir);
            }
        }
    
        public void Execute(GeneratorExecutionContext context)
        {
            if (string.IsNullOrEmpty(_tsOutputDir))
            {
                // 处理属性未配置的 fallback 逻辑
                var baseFilePath = context.Compilation.SyntaxTrees
                    .FirstOrDefault(x => x.HasCompilationUnitRoot)?.FilePath;
                if (string.IsNullOrEmpty(baseFilePath)) return;
                _tsOutputDir = Path.Combine(Path.GetDirectoryName(baseFilePath)!, "tsFiles");
                Directory.CreateDirectory(_tsOutputDir);
            }
    
            // 生成TS内容并写入文件
            var tsContent = "export class MyTsClass {}";
            var tsFilePath = Path.Combine(_tsOutputDir, "MyClass.ts");
            File.WriteAllText(tsFilePath, tsContent);
        }
    }
    

优势:

  • 路径直接来自项目配置,准确可靠,不受语法树文件位置影响
  • 可在Initialize阶段缓存,无需每次Execute重复计算
  • 用户可灵活自定义输出目录,增强工具的扩展性

2. 改进路径缓存逻辑(兼容现有实现)

如果暂时不想依赖MSBuild属性,可通过缓存路径优化现有逻辑,避免重复执行路径计算和目录判断:

[Generator]
public class TsGenerator : ISourceGenerator
{
    private string? _cachedOutputDir;

    public void Execute(GeneratorExecutionContext context)
    {
        if (_cachedOutputDir == null)
        {
            var baseTree = context.Compilation.SyntaxTrees.FirstOrDefault(x => x.HasCompilationUnitRoot);
            if (baseTree == null || string.IsNullOrEmpty(baseTree.FilePath))
            {
                context.ReportDiagnostic(Diagnostic.Create(
                    new DiagnosticDescriptor("TSGEN001", "Missing Source File", 
                    "No valid source file found to determine project path", 
                    "Generator", DiagnosticSeverity.Warning, true),
                    Location.None));
                return;
            }

            var projectDir = Path.GetDirectoryName(baseTree.FilePath);
            _cachedOutputDir = Path.Combine(projectDir!, "tsFiles");
            // 一次性创建目录,无需每次判断存在性
            Directory.CreateDirectory(_cachedOutputDir);
        }

        // 写入TS文件逻辑
        var tsPath = Path.Combine(_cachedOutputDir, "Generated.ts");
        File.WriteAllText(tsPath, "// Generated content");
    }

    public void Initialize(GeneratorInitializationContext context) {}
}

优势:

  • 仅第一次Execute时计算路径和创建目录,后续复用缓存,减少性能损耗
  • 添加空值判断和诊断输出,提升可靠性

3. 使用增量生成器(.NET 6+)

如果目标框架是.NET 6及以上,推荐使用增量生成器,它只会在相关代码变化时执行生成逻辑,大幅减少重复执行次数:

[Generator]
public class TsIncrementalGenerator : IIncrementalGenerator
{
    private string? _tsOutputDir;

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 尝试从MSBuild属性获取路径
        if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(
            "build_property.TsOutputDir", out var outputDir))
        {
            _tsOutputDir = outputDir;
            Directory.CreateDirectory(_tsOutputDir);
        }

        // 定义增量管道:仅当编译变化且路径未缓存时计算路径
        var outputDirProvider = context.CompilationProvider
            .Select((compilation, _) =>
            {
                if (_tsOutputDir != null) return _tsOutputDir;
                var baseTree = compilation.SyntaxTrees.FirstOrDefault(x => x.HasCompilationUnitRoot);
                if (baseTree == null) return null;
                var projectDir = Path.GetDirectoryName(baseTree.FilePath);
                _tsOutputDir = Path.Combine(projectDir!, "tsFiles");
                Directory.CreateDirectory(_tsOutputDir);
                return _tsOutputDir;
            })
            .Where(dir => dir != null);

        // 结合需要生成TS的语法节点(示例:所有类声明)
        var classDeclarations = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => ctx.Node as ClassDeclarationSyntax)
            .Where(cls => cls != null);

        // 合并路径和类声明,生成TS文件
        var generateFiles = outputDirProvider.Combine(classDeclarations.Collect())
            .Select((tuple, _) =>
            {
                var dir = tuple.Left!;
                var classes = tuple.Right!;
                foreach (var cls in classes)
                {
                    var tsContent = $"export class {cls.Identifier.Text} {{}}";
                    var tsPath = Path.Combine(dir, $"{cls.Identifier.Text}.ts");
                    File.WriteAllText(tsPath, tsContent);
                }
                return null;
            });

        // 注册输出
        context.RegisterSourceOutput(generateFiles, (ctx, _) => {});
    }
}

优势:

  • 仅在相关语法节点或编译环境变化时执行生成,性能最优
  • 天然支持路径缓存,避免重复计算

内容的提问来源于stack exchange,提问作者AliSalehi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 11:40:53