2

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="..\MySourceGenerators\MySourceGenerators.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;
}
4

2 回答 2

1

源生成器目标netstandard2.0,您的项目目标net6.0。当您通过PackageReference.

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

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

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

于 2021-10-29T05:57:56.670 回答
0

不幸的是,即使在当前的 VS 2022(版本 17.0.5)中,对该功能的支持也受到了一些限制。正如您所注意到的,VS 显示生成的代码的正确状态的唯一时刻是在 VS 重新启动之后(不仅仅是解决方案加载/卸载,而是应用程序的完全重新启动)。生成器完成后没问题,你只想检查生成了什么,但是在生成器开发过程中很痛苦。所以我在开发过程中最终采用了这样的方法:

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

要强制生成器运行,我们需要强制重建“testproject.csproj”项目。我会使用“testproject”项目目录中的命令行:' dotnet clean; dotnet build'。

生成的文件最终会出现在输出目录中。例如,我们可以使用 VS Code 观看它们。VS Code 不会阻止打开的文件,但任何其他具有非阻塞读取的记事本就足够了。这不是一个理想的解决方案,但此时它消除了生成器开发的主要痛苦:要查看代码生成的实际结果,我们不必重新启动 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;
            while (!done)
            {
                int cnt = 0;
                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);
                }
            }
        }
    }
}

于 2022-01-23T12:07:19.483 回答