Edit3: 在某些时候,这才开始起作用。不知道为什么。也许是 VS 的一个 bug 被修复了?
编辑2:查看解决方案资源管理器中的分析器节点,我发现当我第一次打开程序时,源生成器成功运行,然后它停止,并且仅对我的代码进行了一些更改后,它生成的所有内容都消失了。
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;
}
不幸的是,即使在当前的 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;
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
使用源生成器时,这不是问题。
我认为要让
ProjectReference
在这种情况下工作,您需要添加SetTargetFramework
元数据。
<ItemGroup>
<ProjectReference Include="..\MySourceGenerators\MySourceGenerators.csproj"
OutputItemType="Analyzer"
SetTargetFramework="netstandard2.0"
ReferenceOutputAssembly="false"/>
</ItemGroup>
这可能有用,抱歉现在无法尝试。
就我而言,在引用生成器的项目中...需要将项目引用定义为以下内容:
<ProjectReference Include="..\MySourceGenerator.Project\Sourcegenerator.csproj"
PrivateAssets="all"
ReferenceOutputAssembly="true"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0" />