一、partial范式深度探讨前文介绍了partial范式简化SourceGenerator开发和测试查阅SourceGenerator之partial范式及测试本文讲partial范式开发和nuget打包,与前文有部分重叠,侧重点不同二、本文以自动生成属性为例1. 功能简介场景是通过一个属性获取对象,但不需要这个对象重复创建单例模式就是其中的场景之一这样的代码是千篇一律的,非常适合自动生成代码2. 生成器代码直接套用ValuesGenerator基类查找标记了GenerateLazy的属性和方法预处理为GenerateLazySource对象执行GenerateLazySource问题是查找属性和方法不能使用官方的SyntaxValueProvider.ForAttributeWithMetadataNameForAttributeWithMetadataName只能用来找partial的类这个场景类是partial但需要从方法(或属性)入手一个类可以有多个方法(或属性)被标记,同个方法(或属性)也可以标记生成多个属性为此笔者重写了这部分代替ForAttributeWithMetadataName[Generator(LanguageNames.CSharp)] public class GenerateLazyGenerator() : ValuesGeneratorGenerateLazySource( Attribute, new SyntaxFilter(false, SyntaxKind.PropertyDeclaration, SyntaxKind.MethodDeclaration), GenerateLazyTransform.Instance, new GeneratorExecutorGenerateLazySource()) { /// summary /// Attribute标记 /// /summary public const string Attribute Hand.Cache.GenerateLazyAttribute; }3. GenerateProvider.CreateByAttributeCreateByAttribute用于代替ForAttributeWithMetadataName先遍历SyntaxTree再查找节点并处理为需要的对象这样可以覆盖ForAttributeWithMetadataName的场景并扩展支持更多的需求限于篇幅不展开所有代码,大家可以到源码库查看public static IncrementalValuesProviderTSource CreateByAttributeTSource(IncrementalGeneratorInitializationContext context, string attributeName, ISyntaxFilter filter, IGeneratorTransformTSource transform) { return context.CompilationProvider .SelectMany(GetSyntaxTree) .SelectMany((syntaxTree, cancellationToken) GetAttribute(syntaxTree, attributeName, filter, transform, cancellationToken)) .WithTrackingName(Provider_ByAttribute); }4. GenerateLazyTransform处理由于我们定位的是方法或者属性,类型(TypeDeclarationSyntax)和类型符号(typeSymbol)需要自行获取另外对类型进行了校验,必须含partial修饰符GetPropertyNameByAttribute获取Attribute配置如果当前是属性就处理为LazyPropertySource如果当前是方法就处理为LazyMethodSourceGenerateLazySource是抽象类,LazyPropertySource和LazyMethodSource是GenerateLazySource的子类CheckSource判断是否有重名对象,如果有就不生成(强行会生成编译不过的文件)public class GenerateLazyTransform : IGeneratorTransformGenerateLazySource { public GenerateLazySource? Transform(AttributeContext context, CancellationToken cancellation) { if (cancellation.IsCancellationRequested) return null; var targetNode context.TargetNode; if (targetNode.Parent is not TypeDeclarationSyntax type || !type.Modifiers.IsPartial()) return null; var semanticModel context.SemanticModel; var typeSymbol semanticModel.GetDeclaredSymbol(type, cancellation); if (typeSymbol is null) return null; var compilation semanticModel.Compilation; var attributeType compilation.GetTypeByMetadataName(GenerateLazyGenerator.Attribute); if (attributeType is null) return null; var propertyName GetPropertyNameByAttribute(context.Attributes, attributeType); GenerateLazySource? source null; if (targetNode is PropertyDeclarationSyntax property) { var propertySymbol semanticModel.GetDeclaredSymbol(property, cancellation); if (propertySymbol is not null propertySymbol.Type is INamedTypeSymbol symbol) source new LazyPropertySource(property, type, typeSymbol, propertyName, symbol, property.Modifiers.IsStatic()); } else if (targetNode is MethodDeclarationSyntax method) { var methodSymbol semanticModel.GetDeclaredSymbol(method, cancellation); if (methodSymbol is not null methodSymbol.ReturnType is INamedTypeSymbol symbol) source new LazyMethodSource(method, type, typeSymbol, propertyName, symbol, method.Modifiers.IsStatic()); } // 判断是否已经存在同名属性 // 不存在才返回 if (source is not null CheckSource(source, compilation)) return source; return null; } /// summary /// 判断延迟缓存源对象是否合法 /// /summary /// param namesource/param /// param namecompilation/param /// returns/returns public static bool CheckSource(GenerateLazySource source, Compilation compilation) { var descriptor new SymbolTypeBuilder() .WithProperty() .WithField() .Build(compilation, source.Symbol); // 存在同名属性不生成 if(descriptor.GetProperty(source.PropertyName) is not null) return false; // 存在同名字段不生成 if (descriptor.GetField(source.ValueName) is not null) return false; if (descriptor.GetField(source.StateName) is not null) return false; if (descriptor.GetField(source.LockName) is not null) return false; return true; } // ... }5. GenerateLazySource首先复制原类型,不用管是类,是结构体还是record,是否有命名空间,这些与原类型保持一致即可Clone会清理一些成员(方法、字段、属性等),避免编译出错定义了3个字段1个属性属性的get处理器是用开源项目EasySyntax定义的,非常简洁使用锁和双重判断实现的线程安全懒汉单例模式如果原方法(或属性)是静态的,生成对象也增加静态修饰符GetValueExpression是从原代码中提取代码,这在LazyPropertySource和LazyMethodSources实现稍有不同public abstract class GenerateLazySource(TypeDeclarationSyntax type, INamedTypeSymbol typeSymbol, string propertyName, INamedTypeSymbol propertySymbol, bool isStatic, string valueName, string stateName, string lockName) : IGeneratorSource { public SyntaxGenerator Generate() { var builder SyntaxGenerator.Clone(_type); var _valueField _propertyType.Field(_value.Identifier, SyntaxGenerator.DefaultLiteral) .Private(); var _stateField SyntaxGenerator.BoolType.Field(_state.Identifier, SyntaxGenerator.FalseLiteral) .Private(); var _lockField SyntaxGenerator.LockType.Field(_lock.Identifier, SyntaxFactory.ImplicitObjectCreationExpression()) .Private(); var property _propertyType.Property(_propertyName, CreateAccessor()) .Public(); if (_isStatic) { builder.AddMember(_valueField.Static()); builder.AddMember(_stateField.Static()); builder.AddMember(_lockField.Static()); builder.AddMember(property.Static()); } else { builder.AddMember(_valueField); builder.AddMember(_stateField); builder.AddMember(_lockField); builder.AddMember(property); } return builder; } /// summary /// 构造属性处理器 /// /summary /// returns/returns public AccessorDeclarationSyntax CreateAccessor() { return SyntaxGenerator.PropertyGetDeclaration() .ToBuilder() // if(_state) .If(_state) // return _value .Return(_value) // lock(_lock){ .Lock(_lock) //if(_state) .If(_state) // return _value .Return(_value) // _value GetValue() .Add(_value.Assign(GetValueExpression())) // _state true .Add(_state.Assign(SyntaxGenerator.TrueLiteral)) // } .End() // reurn _value .Return(_value); } protected abstract ExpressionSyntax GetValueExpression(); // ... }6. 按方法生成属性的Case6.1 原始代码using Hand; using Hand.Cache; namespace GenerateCachedPropertyTests; public partial class MethodTests { [GenerateLazy(LazyTime)] public DateTime CreateTime() { return DateTime.Now; } }6.2 生成代码// auto-generated/ namespace GenerateCachedPropertyTests; partial class MethodTests { private System.DateTime _valueLazyTime default; private bool _stateLazyTime false; private object _lockLazyTime new(); public System.DateTime LazyTime { get { if (_stateLazyTime) return _valueLazyTime; lock (_lockLazyTime) { if (_stateLazyTime) return _valueLazyTime; _valueLazyTime DateTime.Now; _stateLazyTime true; } return _valueLazyTime; } } }7. 按属性生成属性的Case7.1 原始代码using Hand; using Hand.Cache; namespace GenerateCachedPropertyTests; public partial class PropertyTests { [GenerateLazy(LazyTime)] public static DateTime Now { get; } DateTime.Now; }7.2 生成代码原属性Now是静态的,生成的LazyTime也是静态的// auto-generated/ namespace GenerateCachedPropertyTests; partial class PropertyTests { private static System.DateTime _valueLazyTime default; private static bool _stateLazyTime false; private static object _lockLazyTime new(); public static System.DateTime LazyTime { get { if (_stateLazyTime) return _valueLazyTime; lock (_lockLazyTime) { if (_stateLazyTime) return _valueLazyTime; _valueLazyTime Now; _stateLazyTime true; } return _valueLazyTime; } } }三、生成器nuget打包技巧1. 开发容易打包难特别是包含依赖项的生成器打包更难首先分享一篇博客园扑克子博主的经验非常感谢这个博主,在此基础上笔者摸索出更好的方法partial范式依赖EasySyntax和GenerateCore还有Microsoft.CodeAnalysis.CSharp的5.0版本如果不打包这些依赖会导致生成器无法正常工作打包方式不对又会导致生成器及依赖项目的dll会出现被调用项目的生成目录正常情况生成器用于编译时代码生成,自己本身不输出到生成目录2. 还是自动生成属性项目为例TargetFramework最好设置为netstandard2.0EnforceExtendedAnalyzerRules最好设置为trueIncludeBuildOutput设置为fase,这是避免生成器本身输出到lib目录引用的IncludeAssets设置为compile和analyzers,compile是为了生成器本身编译,analyzers是为了能用于执行生成器时调用PrivateAssets为compile是为了排除生成器的依赖项目参与调用生成器项目的编译,因为它是本项目私有不继续传递,会被排除最后None配置到analyzers/dotnet/cs就是生成器目录专用另外NoWarn配置为NU5128,是排除警告信息,由于生成器只需要analyzers文件夹,没有lib文件夹导致警告是误报Project SdkMicrosoft.NET.Sdk PropertyGroup TargetFrameworknetstandard2.0/TargetFramework EnforceExtendedAnalyzerRulestrue/EnforceExtendedAnalyzerRules IncludeBuildOutputfalse/IncludeBuildOutput NoWarn$(NoWarn);NU5128/NoWarn /PropertyGroup ItemGroup PackageReference IncludeMicrosoft.CodeAnalysis.Analyzers Version3.11.0 IncludeAssetscompile;analyzers/IncludeAssets PrivateAssetscompile/PrivateAssets /PackageReference PackageReference IncludeMicrosoft.CodeAnalysis.CSharp Version5.0.0 IncludeAssetscompile;analyzers/IncludeAssets PrivateAssetscompile/PrivateAssets /PackageReference /ItemGroup ItemGroup ProjectReference Include..\Hand.GenerateCore\Hand.GenerateCore.csproj IncludeAssetscompile;analyzers/IncludeAssets PrivateAssetscompile/PrivateAssets /ProjectReference ProjectReference Include..\Hand.Generators.EasySyntax\Hand.Generators.EasySyntax.csproj IncludeAssetscompile;analyzers/IncludeAssets PrivateAssetscompile/PrivateAssets /ProjectReference /ItemGroup ItemGroup None Include$(OutputPath)\$(AssemblyName).dll Packtrue PackagePathanalyzers/dotnet/cs Visiblefalse / /ItemGroup /Project3. 生成器依赖项目需要特殊配置EasySyntax和GenerateCore就是生成器依赖项目TargetFramework最好设置为netstandard2.0EnforceExtendedAnalyzerRules最好设置为trueNone也配置到analyzers/dotnet/cs是为了生成器调用这样nuget里面有两份dll,lib的可以直接引用,analyzers里面的生成器专用Project SdkMicrosoft.NET.Sdk PropertyGroup TargetFrameworknetstandard2.0/TargetFramework EnforceExtendedAnalyzerRulestrue/EnforceExtendedAnalyzerRules /PropertyGroup ItemGroup PackageReference IncludeMicrosoft.CodeAnalysis.Analyzers Version3.11.0 / PackageReference IncludeMicrosoft.CodeAnalysis.CSharp Version5.0.0 / /ItemGroup ItemGroup None Include$(OutputPath)\$(AssemblyName).dll Packtrue PackagePathanalyzers/dotnet/cs Visiblefalse / /ItemGroup /Project4. 脚本的方法安装时执行install.ps1卸载时执行uninstall.ps1参考开源项目OneOf.SourceGeneratorparam($installPath, $toolsPath, $package, $project) $analyzersPaths Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) analyzers ) * -Resolve foreach($analyzersPath in $analyzersPaths) { # Install the language agnostic analyzers. if (Test-Path $analyzersPath) { foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) { if($project.Object.AnalyzerReferences) { $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) } } } } # $project.Type gives the language name like (C# or VB.NET) $languageFolder if($project.Type -eq C#) { $languageFolder cs } if($project.Type -eq VB.NET) { $languageFolder vb } if($languageFolder -eq ) { return } foreach($analyzersPath in $analyzersPaths) { # Install language specific analyzers. $languageAnalyzersPath join-path $analyzersPath $languageFolder if (Test-Path $languageAnalyzersPath) { foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) { if($project.Object.AnalyzerReferences) { $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) } } } }四、总结1. 打包方法1需要的文件都通过PackagePath输出到analyzers问题是nuget包会增大,丢失了项目的依赖关系2. 打包方法2通过IncludeAssets和PrivateAssets准确配置包作用域PackagePath只打包当前文件问题是依赖自己不能控制的包(没有analyzers文件夹)不好处理3. 打包方法3通过脚本处理nuget安装和卸载,无需analyzers输出缺点是要写额外脚本4.笔者推荐方法2大家喜欢哪种方法呢
.NET源码生成器基于partial范式开发和nuget打包
发布时间:2026/5/24 17:22:22
一、partial范式深度探讨前文介绍了partial范式简化SourceGenerator开发和测试查阅SourceGenerator之partial范式及测试本文讲partial范式开发和nuget打包,与前文有部分重叠,侧重点不同二、本文以自动生成属性为例1. 功能简介场景是通过一个属性获取对象,但不需要这个对象重复创建单例模式就是其中的场景之一这样的代码是千篇一律的,非常适合自动生成代码2. 生成器代码直接套用ValuesGenerator基类查找标记了GenerateLazy的属性和方法预处理为GenerateLazySource对象执行GenerateLazySource问题是查找属性和方法不能使用官方的SyntaxValueProvider.ForAttributeWithMetadataNameForAttributeWithMetadataName只能用来找partial的类这个场景类是partial但需要从方法(或属性)入手一个类可以有多个方法(或属性)被标记,同个方法(或属性)也可以标记生成多个属性为此笔者重写了这部分代替ForAttributeWithMetadataName[Generator(LanguageNames.CSharp)] public class GenerateLazyGenerator() : ValuesGeneratorGenerateLazySource( Attribute, new SyntaxFilter(false, SyntaxKind.PropertyDeclaration, SyntaxKind.MethodDeclaration), GenerateLazyTransform.Instance, new GeneratorExecutorGenerateLazySource()) { /// summary /// Attribute标记 /// /summary public const string Attribute Hand.Cache.GenerateLazyAttribute; }3. GenerateProvider.CreateByAttributeCreateByAttribute用于代替ForAttributeWithMetadataName先遍历SyntaxTree再查找节点并处理为需要的对象这样可以覆盖ForAttributeWithMetadataName的场景并扩展支持更多的需求限于篇幅不展开所有代码,大家可以到源码库查看public static IncrementalValuesProviderTSource CreateByAttributeTSource(IncrementalGeneratorInitializationContext context, string attributeName, ISyntaxFilter filter, IGeneratorTransformTSource transform) { return context.CompilationProvider .SelectMany(GetSyntaxTree) .SelectMany((syntaxTree, cancellationToken) GetAttribute(syntaxTree, attributeName, filter, transform, cancellationToken)) .WithTrackingName(Provider_ByAttribute); }4. GenerateLazyTransform处理由于我们定位的是方法或者属性,类型(TypeDeclarationSyntax)和类型符号(typeSymbol)需要自行获取另外对类型进行了校验,必须含partial修饰符GetPropertyNameByAttribute获取Attribute配置如果当前是属性就处理为LazyPropertySource如果当前是方法就处理为LazyMethodSourceGenerateLazySource是抽象类,LazyPropertySource和LazyMethodSource是GenerateLazySource的子类CheckSource判断是否有重名对象,如果有就不生成(强行会生成编译不过的文件)public class GenerateLazyTransform : IGeneratorTransformGenerateLazySource { public GenerateLazySource? Transform(AttributeContext context, CancellationToken cancellation) { if (cancellation.IsCancellationRequested) return null; var targetNode context.TargetNode; if (targetNode.Parent is not TypeDeclarationSyntax type || !type.Modifiers.IsPartial()) return null; var semanticModel context.SemanticModel; var typeSymbol semanticModel.GetDeclaredSymbol(type, cancellation); if (typeSymbol is null) return null; var compilation semanticModel.Compilation; var attributeType compilation.GetTypeByMetadataName(GenerateLazyGenerator.Attribute); if (attributeType is null) return null; var propertyName GetPropertyNameByAttribute(context.Attributes, attributeType); GenerateLazySource? source null; if (targetNode is PropertyDeclarationSyntax property) { var propertySymbol semanticModel.GetDeclaredSymbol(property, cancellation); if (propertySymbol is not null propertySymbol.Type is INamedTypeSymbol symbol) source new LazyPropertySource(property, type, typeSymbol, propertyName, symbol, property.Modifiers.IsStatic()); } else if (targetNode is MethodDeclarationSyntax method) { var methodSymbol semanticModel.GetDeclaredSymbol(method, cancellation); if (methodSymbol is not null methodSymbol.ReturnType is INamedTypeSymbol symbol) source new LazyMethodSource(method, type, typeSymbol, propertyName, symbol, method.Modifiers.IsStatic()); } // 判断是否已经存在同名属性 // 不存在才返回 if (source is not null CheckSource(source, compilation)) return source; return null; } /// summary /// 判断延迟缓存源对象是否合法 /// /summary /// param namesource/param /// param namecompilation/param /// returns/returns public static bool CheckSource(GenerateLazySource source, Compilation compilation) { var descriptor new SymbolTypeBuilder() .WithProperty() .WithField() .Build(compilation, source.Symbol); // 存在同名属性不生成 if(descriptor.GetProperty(source.PropertyName) is not null) return false; // 存在同名字段不生成 if (descriptor.GetField(source.ValueName) is not null) return false; if (descriptor.GetField(source.StateName) is not null) return false; if (descriptor.GetField(source.LockName) is not null) return false; return true; } // ... }5. GenerateLazySource首先复制原类型,不用管是类,是结构体还是record,是否有命名空间,这些与原类型保持一致即可Clone会清理一些成员(方法、字段、属性等),避免编译出错定义了3个字段1个属性属性的get处理器是用开源项目EasySyntax定义的,非常简洁使用锁和双重判断实现的线程安全懒汉单例模式如果原方法(或属性)是静态的,生成对象也增加静态修饰符GetValueExpression是从原代码中提取代码,这在LazyPropertySource和LazyMethodSources实现稍有不同public abstract class GenerateLazySource(TypeDeclarationSyntax type, INamedTypeSymbol typeSymbol, string propertyName, INamedTypeSymbol propertySymbol, bool isStatic, string valueName, string stateName, string lockName) : IGeneratorSource { public SyntaxGenerator Generate() { var builder SyntaxGenerator.Clone(_type); var _valueField _propertyType.Field(_value.Identifier, SyntaxGenerator.DefaultLiteral) .Private(); var _stateField SyntaxGenerator.BoolType.Field(_state.Identifier, SyntaxGenerator.FalseLiteral) .Private(); var _lockField SyntaxGenerator.LockType.Field(_lock.Identifier, SyntaxFactory.ImplicitObjectCreationExpression()) .Private(); var property _propertyType.Property(_propertyName, CreateAccessor()) .Public(); if (_isStatic) { builder.AddMember(_valueField.Static()); builder.AddMember(_stateField.Static()); builder.AddMember(_lockField.Static()); builder.AddMember(property.Static()); } else { builder.AddMember(_valueField); builder.AddMember(_stateField); builder.AddMember(_lockField); builder.AddMember(property); } return builder; } /// summary /// 构造属性处理器 /// /summary /// returns/returns public AccessorDeclarationSyntax CreateAccessor() { return SyntaxGenerator.PropertyGetDeclaration() .ToBuilder() // if(_state) .If(_state) // return _value .Return(_value) // lock(_lock){ .Lock(_lock) //if(_state) .If(_state) // return _value .Return(_value) // _value GetValue() .Add(_value.Assign(GetValueExpression())) // _state true .Add(_state.Assign(SyntaxGenerator.TrueLiteral)) // } .End() // reurn _value .Return(_value); } protected abstract ExpressionSyntax GetValueExpression(); // ... }6. 按方法生成属性的Case6.1 原始代码using Hand; using Hand.Cache; namespace GenerateCachedPropertyTests; public partial class MethodTests { [GenerateLazy(LazyTime)] public DateTime CreateTime() { return DateTime.Now; } }6.2 生成代码// auto-generated/ namespace GenerateCachedPropertyTests; partial class MethodTests { private System.DateTime _valueLazyTime default; private bool _stateLazyTime false; private object _lockLazyTime new(); public System.DateTime LazyTime { get { if (_stateLazyTime) return _valueLazyTime; lock (_lockLazyTime) { if (_stateLazyTime) return _valueLazyTime; _valueLazyTime DateTime.Now; _stateLazyTime true; } return _valueLazyTime; } } }7. 按属性生成属性的Case7.1 原始代码using Hand; using Hand.Cache; namespace GenerateCachedPropertyTests; public partial class PropertyTests { [GenerateLazy(LazyTime)] public static DateTime Now { get; } DateTime.Now; }7.2 生成代码原属性Now是静态的,生成的LazyTime也是静态的// auto-generated/ namespace GenerateCachedPropertyTests; partial class PropertyTests { private static System.DateTime _valueLazyTime default; private static bool _stateLazyTime false; private static object _lockLazyTime new(); public static System.DateTime LazyTime { get { if (_stateLazyTime) return _valueLazyTime; lock (_lockLazyTime) { if (_stateLazyTime) return _valueLazyTime; _valueLazyTime Now; _stateLazyTime true; } return _valueLazyTime; } } }三、生成器nuget打包技巧1. 开发容易打包难特别是包含依赖项的生成器打包更难首先分享一篇博客园扑克子博主的经验非常感谢这个博主,在此基础上笔者摸索出更好的方法partial范式依赖EasySyntax和GenerateCore还有Microsoft.CodeAnalysis.CSharp的5.0版本如果不打包这些依赖会导致生成器无法正常工作打包方式不对又会导致生成器及依赖项目的dll会出现被调用项目的生成目录正常情况生成器用于编译时代码生成,自己本身不输出到生成目录2. 还是自动生成属性项目为例TargetFramework最好设置为netstandard2.0EnforceExtendedAnalyzerRules最好设置为trueIncludeBuildOutput设置为fase,这是避免生成器本身输出到lib目录引用的IncludeAssets设置为compile和analyzers,compile是为了生成器本身编译,analyzers是为了能用于执行生成器时调用PrivateAssets为compile是为了排除生成器的依赖项目参与调用生成器项目的编译,因为它是本项目私有不继续传递,会被排除最后None配置到analyzers/dotnet/cs就是生成器目录专用另外NoWarn配置为NU5128,是排除警告信息,由于生成器只需要analyzers文件夹,没有lib文件夹导致警告是误报Project SdkMicrosoft.NET.Sdk PropertyGroup TargetFrameworknetstandard2.0/TargetFramework EnforceExtendedAnalyzerRulestrue/EnforceExtendedAnalyzerRules IncludeBuildOutputfalse/IncludeBuildOutput NoWarn$(NoWarn);NU5128/NoWarn /PropertyGroup ItemGroup PackageReference IncludeMicrosoft.CodeAnalysis.Analyzers Version3.11.0 IncludeAssetscompile;analyzers/IncludeAssets PrivateAssetscompile/PrivateAssets /PackageReference PackageReference IncludeMicrosoft.CodeAnalysis.CSharp Version5.0.0 IncludeAssetscompile;analyzers/IncludeAssets PrivateAssetscompile/PrivateAssets /PackageReference /ItemGroup ItemGroup ProjectReference Include..\Hand.GenerateCore\Hand.GenerateCore.csproj IncludeAssetscompile;analyzers/IncludeAssets PrivateAssetscompile/PrivateAssets /ProjectReference ProjectReference Include..\Hand.Generators.EasySyntax\Hand.Generators.EasySyntax.csproj IncludeAssetscompile;analyzers/IncludeAssets PrivateAssetscompile/PrivateAssets /ProjectReference /ItemGroup ItemGroup None Include$(OutputPath)\$(AssemblyName).dll Packtrue PackagePathanalyzers/dotnet/cs Visiblefalse / /ItemGroup /Project3. 生成器依赖项目需要特殊配置EasySyntax和GenerateCore就是生成器依赖项目TargetFramework最好设置为netstandard2.0EnforceExtendedAnalyzerRules最好设置为trueNone也配置到analyzers/dotnet/cs是为了生成器调用这样nuget里面有两份dll,lib的可以直接引用,analyzers里面的生成器专用Project SdkMicrosoft.NET.Sdk PropertyGroup TargetFrameworknetstandard2.0/TargetFramework EnforceExtendedAnalyzerRulestrue/EnforceExtendedAnalyzerRules /PropertyGroup ItemGroup PackageReference IncludeMicrosoft.CodeAnalysis.Analyzers Version3.11.0 / PackageReference IncludeMicrosoft.CodeAnalysis.CSharp Version5.0.0 / /ItemGroup ItemGroup None Include$(OutputPath)\$(AssemblyName).dll Packtrue PackagePathanalyzers/dotnet/cs Visiblefalse / /ItemGroup /Project4. 脚本的方法安装时执行install.ps1卸载时执行uninstall.ps1参考开源项目OneOf.SourceGeneratorparam($installPath, $toolsPath, $package, $project) $analyzersPaths Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) analyzers ) * -Resolve foreach($analyzersPath in $analyzersPaths) { # Install the language agnostic analyzers. if (Test-Path $analyzersPath) { foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) { if($project.Object.AnalyzerReferences) { $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) } } } } # $project.Type gives the language name like (C# or VB.NET) $languageFolder if($project.Type -eq C#) { $languageFolder cs } if($project.Type -eq VB.NET) { $languageFolder vb } if($languageFolder -eq ) { return } foreach($analyzersPath in $analyzersPaths) { # Install language specific analyzers. $languageAnalyzersPath join-path $analyzersPath $languageFolder if (Test-Path $languageAnalyzersPath) { foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) { if($project.Object.AnalyzerReferences) { $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) } } } }四、总结1. 打包方法1需要的文件都通过PackagePath输出到analyzers问题是nuget包会增大,丢失了项目的依赖关系2. 打包方法2通过IncludeAssets和PrivateAssets准确配置包作用域PackagePath只打包当前文件问题是依赖自己不能控制的包(没有analyzers文件夹)不好处理3. 打包方法3通过脚本处理nuget安装和卸载,无需analyzers输出缺点是要写额外脚本4.笔者推荐方法2大家喜欢哪种方法呢