C# 源代码生成器不包括来自项目参考的结果

Edit3: 在某些时候,这才刚刚开始工作。不知道为什么。也许这是一个修复的VS错误?

Edit2: 查看解决方案资源管理器中的 Analyzers 节点,我发现源生成器在我第一次打开程序时成功运行,然后它停止并且它生成的所有内容在我的代码只进行了一些更改后就消失了。

immediately after opening solution:
> Analyzers
>> MySourceGenerators
>>> MySourceGenerators.NotifyPropertyChangesGenerator
>>>> _NotifyChangedClass_Notify.cs

after making any edits
> Analyzers
>> MySourceGenerators
>>> MySourceGenerators.NotifyPropertyChangesGenerator
>>>> This generator is not generating files.

编辑: 在按照注释的建议调用 Debugger.Launch() 后,我可以确认生成器代码正在运行,并且源文本看起来与预期的完全一样。但是 IDE 和编译器仍然会出现错误,就好像没有包含结果一样。

我正在尝试设置一个源生成器以从本地项目引用运行,但无法使其实际运行。我的 NUnit 测试通过了,所以我知道实际的生成逻辑很好,但是准系统测试项目在 Visual Studio 中都无法编译并报告错误。我正在使用 Visual Studio 2022 Preview 5.0,以防万一。

<--generator.csproj-->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>10</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IncludeBuildOutpout>false</IncludeBuildOutpout>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
  </ItemGroup>

</Project>
<--testproject.csproj-->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..MySourceGeneratorsMySourceGenerators.csproj" 
                      OutputItemType="Analyzer"
                      ReferenceOutputAssembly="false"/>
  </ItemGroup>

</Project>
//generator.cs
[Generator]
public class NotifyPropertyChangesGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        var receiver = (NotifySyntaxReceiver)context.SyntaxReceiver!;

        if (receiver.Classes.Count > 0)
        {
            foreach (var c in receiver.Classes)
            {
                /* Generate the source */

                var source = SyntaxFactory.ParseCompilationUnit(builder.ToString())
                    .NormalizeWhitespace()
                    .GetText(Encoding.UTF8, Microsoft.CodeAnalysis.Text.SourceHashAlgorithm.Sha256);

                context.AddSource($"_{c.ClassDeclaration.Identifier.ValueText}_Notify", source);
            }
        }
    }

    public void Initialize(GeneratorInitializationContext context)
    {
        context.RegisterForSyntaxNotifications(() => new NotifySyntaxReceiver());
    }

}

class NotifySyntaxReceiver : ISyntaxReceiver
{
    public List<NotifyClass> Classes { get; } = new();

    public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
    {

        if (syntaxNode is ClassDeclarationSyntax cds)
        {
            /* Identify classes that need generation */
        }
    }
}
//testproject.cs
internal class NotifyChangedClass : INotifyPropertyChanged
{
    string n_Property;
}
stack overflow C# Source Generator not including results from Project Reference
原文答案

答案:

作者头像

不幸的是,即使在当前的VS 2022(17.0.5版)中,对此功能的支持也受到限制。正如您注意到的那样,VS显示正确生成的代码状态的唯一时刻是在VS重新启动之后(不仅仅是解决方案加载/卸载,而是应用程序的完整重新启动)。当发电机完成时,这不是问题,您只想检查生成的内容,但是在发电机开发过程中是痛苦。因此,我在开发过程中采用了这样的方法:

在给定发电机的调试/开发过程中,我们可以将生成的文件的输出添加到编译上下文中,而且可以在文件系统的临时目录中或仅在临时目录中添加到临时目录中,直到我们对结果感到满意。

为了强迫发电机运行,我们需要强制重建“ TestProject.csproj”项目。我将使用“ TestProject”项目目录:' dotnet clean; dotnet build '中的命令行。

生成的文件最终将进入输出目录。例如,我们可以使用VS代码观看它们。 VS代码不会阻止打开的文件,但是任何其他具有非阻止读数的记事本都足够了。这不是理想的解决方案,但此刻,它消除了发电机开发的主要痛苦:要查看代码生成的实际结果,我们不必重新启动VS。

示例代码草稿“ generator.csproj”项目:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace Target.Generators
{
    [Generator]
    public class TargetGenerator : ISourceGenerator
    {
        private readonly ISourceBuilder _sourceBuilder;

        public TargetGenerator()
        {
            _sourceBuilder = new SourceBuilder();
        }

        public void Initialize(GeneratorInitializationContext context) =>
            _sourceBuilder.Initialize(context);

        public void Execute(GeneratorExecutionContext context)
        {
            // Uncomment these to lines to start debugging the generator in the separate VS instance
            //// Debugger.Launch();
            //// Debugger.Break();

            // comment/uncomment these lines to use ether 'default' or 'debug' source file writer
            ////var fileWriter = new DefaultSourceFileWriter(context);
            var fileWriter = new DebugSourceFileWriter(context, "C:code-gen");
            var fileBuilders = _sourceBuilder.Build(context);

            fileWriter.WriteFiles(fileBuilders);
        }
    }

    public interface ISourceBuilder
    {
        void Initialize(GeneratorInitializationContext context);
        IEnumerable<(string Filename, string Source)> Build(GeneratorExecutionContext context);
    }

    public class SourceBuilder : ISourceBuilder
    {
        public void Initialize(GeneratorInitializationContext context)
        {
        }

        public IEnumerable<(string Filename, string Source)> Build(GeneratorExecutionContext context)
        {
            // Here should be an actual source code generator implementation
            throw new NotImplementedException();
        }
    }

    public interface ISourceFileWriter
    {
        void WriteFiles(IEnumerable<(string Filename, string Source)> sourceFiles);
    }

    public class DefaultSourceFileWriter : ISourceFileWriter
    {
        private readonly GeneratorExecutionContext _context;

        public DefaultSourceFileWriter(GeneratorExecutionContext context)
        {
            _context = context;
        }

        public void WriteFiles(IEnumerable<(string Filename, string Source)> sourceFiles)
        {
            foreach (var sourceFile in sourceFiles)
            {
                AddFile(sourceFile);
            }
        }

        protected virtual void AddFile((string Filename, string Source) sourceFile)
        {
            _context.AddSource(
                sourceFile.Filename,
                SourceText.From(sourceFile.Source, Encoding.UTF8));
        }
    }

    public class DebugSourceFileWriter : DefaultSourceFileWriter
    {
        private readonly string _outputDirectoryRoot;

        public DebugSourceFileWriter(
            GeneratorExecutionContext context,
            string outputDirectoryRoot)
            : base(context)
        {
            _outputDirectoryRoot = outputDirectoryRoot;
        }

        protected override void AddFile((string Filename, string Source) sourceFile)
        {
            bool done = false;
            int cnt = 0;
            while (!done)
            {
                try
                {
                    var fullFileName = Path.Combine(_outputDirectoryRoot, sourceFile.Filename);
                    File.WriteAllText(fullFileName, sourceFile.Source, Encoding.UTF8);
                    done = true;
                }
                catch
                {
                    cnt++;
                    if (cnt > 5)
                    {
                        done = true;
                    }

                    Thread.Sleep(100);
                }
            }
        }
    }
}
作者头像

源生成器以 netstandard2.0 为目标,您的项目以 net6.0 为目标。当您通过 PackageReference 使用源生成器时,这不是问题。

thinkProjectReference 在这种情况下工作,您需要添加 SetTargetFramework 元数据。

  <ItemGroup>
    <ProjectReference Include="..MySourceGeneratorsMySourceGenerators.csproj" 
                      OutputItemType="Analyzer"
                      SetTargetFramework="netstandard2.0"
                      ReferenceOutputAssembly="false"/>
  </ItemGroup>

这可能有效,抱歉现在不能尝试。

相关问题