wordpress获取文章发布时间,网站怎么优化自己免费,佛山外贸网站建设机构,乾安网站建设公司电话C#源代码生成器
01 源代码生成器初体验
新建一个类库#xff0c;一定是standard2.0版本#xff0c;否则会出问题。引用Nuget包Microsoft.CodeAnalysis.Common新建一个类#xff0c;继承自ISourceGenerator接口
//一定要写#xff0c;制定语言
[Generator(LanguageNames.…C#源代码生成器
01 源代码生成器初体验
新建一个类库一定是standard2.0版本否则会出问题。引用Nuget包Microsoft.CodeAnalysis.Common新建一个类继承自ISourceGenerator接口
//一定要写制定语言
[Generator(LanguageNames.CSharp)]
public sealed class GreetingGenerator : ISourceGenerator
{//源代码生成器的所要生成的方法public void Execute(GeneratorExecutionContext context){//建议名称使用.g.cs//建议使用全局命名空间global:: 为了防止诸如System和Windows.System冲突context.AddSource(Greeting.g.cs,$$//加上这句话告知编译器这个文件是由源代码生成器生成的//防止编译器进行代码分析避免不必要的编译器警告//auto-generatednamespace GreetingTest;//配置预处理指令#nullable enable//告知源代码生成器生成的代码[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] //告知由哪个源代码生成器生成的代码[global::System.CodeDom.Compiler.GeneratedCodeAttribute({{nameof(GreetingGenerator)}},1.0)] public static class Greeting{//告知源代码生成器生成的代码[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] //告知由哪个源代码生成器生成的代码[global::System.CodeDom.Compiler.GeneratedCodeAttribute({{nameof(GreetingGenerator)}},1.0)] public static void SayHello(string name){global::System.Console.WriteLine($Hello, World {name}!);}});}//源代码生成器本身的初始化方法public void Initialize(GeneratorInitializationContext context){}
}注意事项
在使用某些方法或者特性时最好写全命名空间并用global::命名空间限定符文件名称建议使用.g.cs后缀建议在开头增加auto-generated注释可以使用原生字符串符号三个双引号以及双内插符号$$,这样可以使用{{}}来进行内插 建立一个控制台项目并引用刚才的类库要加上OutputItemType和 ReferenceOutAssembly ItemGroup!--ReferenceOutAssembly设定false表示不会将生成器的作为引用而是将分析器生成的代码。--ProjectReference Include..\SourceGeneratorConsole.Generator\SourceGeneratorConsole.Generator.csproj OutputItemTypeAnalyzer ReferenceOutAssemblyfalse /
/ItemGroup使用源代码生成器使用生成器中所生成的方法。
using GreetingTest;
Greeting.SayHello(李四);02 使用分部类型
很多时候不需要源代码生成器生成完整的类型而是和主程序交互分别形成一定的代码此时可以使用分部类型来实现。
在上一章节中的控制台项目中增加一个类
namespace GreetingTest
{public static partial class GreetingUsePartialClass{public static partial void SayHello(string name);}
}修改上一章节中源代码生成器类库项目
namespace SourceGeneratorConsole.Generator;[Generator(LanguageNames.CSharp)]
public sealed class GreetingGenerator : ISourceGenerator
{//源代码生成器的所要生成的方法public void Execute(GeneratorExecutionContext context){//修改为GreetingUsePartialClass.g.cs和控制台中定义的名称相对应context.AddSource(GreetingUsePartialClass.g.cs,$$//auto-generatednamespace GreetingTest;//分部类可以省略public static等只要在一个地方定义了就可以了partial class GreetingUsePartialClass{//分部方法必须写全public static partial void SayHello(string name){global::System.Console.WriteLine($Hello, World {name}!);}});}//源代码生成器本身的初始化方法public void Initialize(GeneratorInitializationContext context){}
}在控制台应用中调用
static void Main(string[] args)
{GreetingUsePartialClass.SayHello(Source Generator);Console.Read();
}03 使用SyntaxReceiver属性
上一章节中在源代码生成器中将类名和方法名写进去了源代码生成器往往是应用在不同的项目中类型名和方法名都不是固定的所以要动态的修改名称这就要用到了SyntaxContextReceiver属性。
在上一章节中的源代码生成器文件中写一个SyntaxReceiver类
//file只在本文件可以用跟internal一样是访问修饰符
//提供一个语法搜索类型这个类型只用于寻找主要项目里的指定语法满足条件部分
file sealed class SyntaxReceiver:ISyntaxReceiver
{//表示一个方法的语法节点这个方法就是用到的SayHello方法,这个方法的返回值是void,静态、partialpublic MethodDeclarationSyntax? SayHelloToMethodSyntaxNode {private set; get; }public void OnVisitSyntaxNode(SyntaxNode syntaxNode){//检查syntaxNode是否是类型定义且Modifiers属性不为空if (syntaxNode is not TypeDeclarationSyntax { Modifiers:var modifiers and not [] }){return;}//如果类型不包含partial关键字if (!modifiers.Any(SyntaxKind.PartialKeyword)){return;}//判断子节点也就是类型内部的成员是否有partialforeach (var childrenNode in syntaxNode.ChildNodes()){// 判断当前语法节点是否是一个合理的方法定义。// 该方法名为 SayHelloTo// 该方法返回一个 void 类型。// 该方法还需要额外的修饰符一会儿要用来判断 partial 关键字。if (childrenNode is not MethodDeclarationSyntax { Identifier:{ ValueText: SayHello },ReturnType:PredefinedTypeSyntax{Keyword.RawKind:(int)SyntaxKind.VoidKeyword},Modifiers:var childrenModifiers and not []} possibleMethodDeclarationSyntax){continue;}// 该方法必须有 partial 关键字的存在。if (!childrenModifiers.Any(SyntaxKind.PartialKeyword)){continue;}if (SayHelloToMethodSyntaxNode is null){SayHelloToMethodSyntaxNode possibleMethodDeclarationSyntax;return;}}}
}修改属性生成器
[Generator(LanguageNames.CSharp)]
public sealed class GreetingGenerator : ISourceGenerator
{//源代码生成器的所要生成的方法public void Execute(GeneratorExecutionContext context){var syntaxReceiver (SyntaxReceiver)context.SyntaxReceiver;//{}为属性模式匹配在此处表示不为空not {}表示为空if (syntaxReceiver.SayHelloToMethodSyntaxNode is not {} methodSyntax){return;}var type methodSyntax.Ancestors().OfTypeTypeDeclarationSyntax().First();var typeName type.Identifier.ValueText;//建议名称使用.g.cs//建议使用全局命名空间global:: 为了防止诸如System和Windows.System冲突context.AddSource(${typeName}.g.cs,$$//加上这句话告知编译器这个文件是由源代码生成器生成的//防止编译器进行代码分析避免不必要的编译器警告//auto-generatednamespace GreetingTest;partial class {{typeName}}{public static partial void SayHello(string name){global::System.Console.WriteLine($Hello, World {name}!);}});}//源代码生成器本身的初始化方法public void Initialize(GeneratorInitializationContext context){//注册一个语法的通知类型作用是运行源代码生成器的时候,去检查固定语法是否满足条件context.RegisterForSyntaxNotifications(() new SyntaxReceiver());}
}在Initialize中返回刚才创建的类 Execute方法中获得相应的类名称。
调用
static void Main(string[] args)
{GreetingUsePartialClass.SayHello(Source Generator);Console.Read();
}04 调试源代码生成器
源代码生成器是在编译阶段中自动生成一般无法调试这时可以在源代码生成器中的Initialize方法中加上
//添加调试器如果程序没有调试器的时候就启动
//如果用了多个源代码生成器只要有一个配置了这个也可以调试其他的
//if (!Debugger.IsAttached)
//{
// Debugger.Launch();
//}05 ISyntaxContextReceiver属性
上面是已知有了SayHello的方法假设不知道是什么方法名如何使用源代码生成器本节借助特性来实现
在主项目中声明特性一般都是放在主项目中因为在主项目中的引用其他项目的设置中已设置了OutputItemTypeAnalyzer ReferenceOutAssemblyfalse这表示不会将生成器的作为引用而是将分析器生成的代码如果将特性定义在生成器中主项目引用不到特性定义
namespace SourceGeneratorConsole
{[AttributeUsage(AttributeTargets.Method,AllowMultiple false,Inherited false)]public sealed class SayHelloAttribute:Attribute; //新语法特性可以直接使用分号结束
}在主项目中声明一个分部方法
namespace SourceGeneratorConsole
{public partial class GreetingUseAttribute{[SayHello]public static partial void SayHi(string name);}
}按照上面的流程创建源代码生成器
namespace SourceGeneratorConsole.UseAttributes
{[Generator(LanguageNames.CSharp)]public sealed class GreetingGenerator : ISourceGenerator{public void Execute(GeneratorExecutionContext context){if (context is not { SyntaxContextReceiver: SyntaxContextReceiver { FoundSymbolPairs: var methodSymbols and not [] } }){return;}foreach (var methodSymbol in methodSymbols){//获取对应的class类型var containingType methodSymbol.ContainingType;//获取完整命名空间名称包括globalvar namespaceName containingType.ContainingNamespace.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);var namspaceString namespaceName[global::.Length..];//查看到底是什么类型var typeKindString containingType.TypeKind switch{TypeKind.Class class,TypeKind.Struct struct,TypeKind.Interface interface,_ throw new InvalidOperationException(错误类型)} ;var syntaxNode (MethodDeclarationSyntax)methodSymbol.DeclaringSyntaxReferences[0].GetSyntax();context.AddSource(${containingType.Name}.g.cs, $$//加上这句话告知编译器这个文件是由源代码生成器生成的//防止编译器进行代码分析避免不必要的编译器警告//auto-generatednamespace {{namspaceString}};partial {{typeKindString}} {{containingType.Name}}{{{syntaxNode.Modifiers}} void {{methodSymbol.Name}}(string name){global::System.Console.WriteLine($Hello, World {name}!);}});}}public void Initialize(GeneratorInitializationContext context){context.RegisterForSyntaxNotifications(() new SyntaxContextReceiver());}}//带有语法上下文的接口,获取所有标记了SayHelloAttribute的方法file sealed class SyntaxContextReceiver : ISyntaxContextReceiver{//表示找到方法的定义信息public ListIMethodSymbol FoundSymbolPairs { get; } new();public void OnVisitSyntaxNode(GeneratorSyntaxContext context){//判别当前语法是否为方法//如果是还要是分部方法//如果满足获取编译信息和语义信息if (context is not { Node: MethodDeclarationSyntax { Modifiers: var modifiers and not [] } methodSytax, SemanticModel: { Compilation: var compolation } semanticModel }){return;}//上面的替代方式// var node context.Node;//语法节点// if (node is not MethodDeclarationSyntax methodSyntax)// {// return;// }// var semanticModel context.SemanticModel;//具有更多语义信息的模型// var compolation semanticModel.Compilation;//编译信息if (!modifiers.Any(SyntaxKind.PartialKeyword)){return;}var attribute compolation.GetTypeByMetadataName(SourceGeneratorConsole.SayHelloAttribute)!;//通过全名称var methodSymbol semanticModel.GetDeclaredSymbol(methodSytax)!;//获取定义信息//判断是否有特性要用SymbolEqualityComparer.Default.Equals来进行比较bool hasAttribute methodSymbol.GetAttributes().Any(e SymbolEqualityComparer.Default.Equals(e.AttributeClass, attribute));if (!hasAttribute){return;}//方法必须返回void而且有一个string参数if (methodSymbol is not { ReturnsVoid: true, Parameters: [{ Type.SpecialType:SpecialType.System_String}] }){return;}FoundSymbolPairs.Add(methodSymbol);}}
}使用源代码生成器
GreetingUseAttribute.SayHi(使用特性的属性生成器);06 自定义MyTuble类型实战
我们经常用到Func泛型委托该泛型委托最多支持16个参数和一个返回值因为泛型定义没有类似于可变参数的功能对于不同数量的泛型参数一定要定义同数量的泛型定义。类似于下面这样。
FuncTResult
FuncT, TResult
FuncT1, T2, TResult
FuncT1, T2, T3, TResult
FuncT1, T2, T3, T4, TResult
FuncT1, T2, T3, T4, T5, TResult
FuncT1, T2, T3, T4, T5, T6, TResult
FuncT1, T2, T3, T4, T5, T6, T7, TResult
FuncT1, T2, T3, T4, T5, T6, T7, T8, TResult我们仿照Func泛型委托自定义一个MyTuple泛型类型
先定义一个MyTuple模板这是一个定义了2个泛型参数的MyTuple类型根据该模板要定义支持多个泛型参数的MyTuple类型
public readonly struct MyTupleT1, T2(T1 value1, T2 value2) : IEqualityOperatorsMyTupleT1, T2, MyTupleT1, T2, bool where T1 : IEqualityOperatorsT1, T1, bool where T2 : IEqualityOperatorsT2, T2, bool
{public T1 Value1 { get; } value1;public T2 Value2 { get; } value2;public static bool operator (MyTupleT1, T2 left, MyTupleT1, T2 right){return left.Value1 right.Value1 left.Value2 right.Value2;}public static bool operator !(MyTupleT1, T2 left, MyTupleT1, T2 right){return !(left right);}
}写一个源代码生成器根据上面的模板进行改造自动生成含有1-8个泛型参数的MyTuple类型其根本原理就是字符串的操作。
[Generator(LanguageNames.CSharp)]
public class MyTupleGenerator : ISourceGenerator
{public void Execute(GeneratorExecutionContext context){var list new Liststring();for (int i 2; i 8; i){var indices Enumerable.Range(1, i).ToArray();var genericArgs ${string.Join(, , from index in indicesselect $T{index} )};var ctorArgs string.Join(, , from index in indicesselect $T{index} value{index});var constraints string.Join(\r\n\t,from index in indicesselect $where T{index}: global::System.Numerics.IEqualityOperatorsT{index},T{index},bool);var properties string.Join(\r\n\t,from index in indicesselect $public T{index} Value{index} {{ get; }}value{index};);var comparison string.Join( , from index in indicesselect $left.Value{index} right.Value{index});list.Add($$public readonly struct MyTuple{{genericArgs}}({{ctorArgs}}):global::System.Numerics.IEqualityOperatorsMyTuple{{genericArgs}},MyTuple{{genericArgs}},bool{{constraints}}{{{properties}}public static bool operator (MyTuple{{genericArgs}} left, MyTuple{{genericArgs}} right){return {{comparison}};}public static bool operator !(MyTuple{{genericArgs}} left, MyTuple{{genericArgs}} right){return !(left right);}});}context.AddSource(MyTuple.g.cs, $$//auto-generated/namespace System;{{string.Join(\r\n\r\n,list)}});}public void Initialize(GeneratorInitializationContext context){}
}主项目引用源代码生成器后使用MyTuple
var myTuple1 new MyTupleint, double(1, 3.0);
var myTuple2 new MyTupleint, double(1, 3.0);
var myTuple3 new MyTupleint, double,float(1, 3.0,5.6f);
var myTuple4 new MyTupleint, double,float(1, 3.0,5.6f);
var myTuple5 new MyTupleint, double,float,uint(1, 3.0,5.6f,8);
var myTuple6 new MyTupleint, double,float,uint(1, 3.0,5.6f,7);Console.WriteLine(myTuple2 myTuple1);
Console.WriteLine(myTuple4 myTuple3);
Console.WriteLine(myTuple6 myTuple5);07AdditionalFiles的使用
上一章节中我们在直接定义了MyTuple时设置最大泛型参数数量为8如果我们需要根据需要来设置最大泛型参数数量则可以在主项目中增加一个配置文件文件中对此进行设置并在源代码生成器中使用GeneratorExecutionContext的AdditionalFiles属性来处理非代码文件。
在主项目中增加一个文件本次案例增加一个MyTupleMaxTypeArgumentCount.txt文件在该文件中写入4。在主项目配置中增加
ItemGroupAdditionalFiles IncludeMyTupleMaxTypeArgumentCount.txt/
/ItemGroup在06章节中源代码基础上增加读取本地文件功能
[Generator(LanguageNames.CSharp)]
public class MyTupleGenerator : ISourceGenerator
{public void Execute(GeneratorExecutionContext context){var maxCount 8;//读取本地文件var additionalFiles context.AdditionalFiles;if (additionalFiles is [{ Path: var path }]){var result File.ReadAllText(path);var regex new Regex(\d);if (regex.Match(result) is { Success:true,Value:var v} int.TryParse(v,out var value) value is 2 and 8){maxCount value;}}var list new Liststring();for (int i 2; i maxCount; i){......//忽略参考06章节}......//忽略参考06章节}
}08自定义编译器诊断信息
在进行编译时编译器会自动给出编译信息供用户查看通常编译器诊断信息如下所示。 由于源代码生成器会自动后台生成所以给出诊断信息是十分必要的。本章节根据07章节中的章节给出自定义编译器诊断信息的
[Generator(LanguageNames.CSharp)]
public class MyTupleGenerator : ISourceGenerator
{//首先创建一个DiagnosticDescriptorstatic readonly DiagnosticDescriptor descriptor new DiagnosticDescriptor(SG0001,//代码可自定义格式一般为 两个字母四位数字本地配置文件错误,源代码生成器生成成功但本地配置文件有错误。{0},SourceGenerator, //此处可以用占位符DiagnosticSeverity.Warning,//提示类别true, 源代码生成器生成成功但本地配置文件有错误。);public void Execute(GeneratorExecutionContext context){var maxCount 8;//读取本地文件var additionalFiles context.AdditionalFiles;if (additionalFiles is [{ Path: var path }]){var result File.ReadAllText(path);var regex new Regex(\d);var match regex.Match(result);if(!match.Success){//给出编译器信息后面的文字则是在descriptor中流出的占位符context.ReportDiagnostic(Diagnostic.Create(descriptor,Location.None, 配置文件的内容并不是一个数字)); //此处不能return因为此处不成立要使用默认值maxCount 8采用goto语句goto nextStep;}var v match.Value;if (!int.TryParse(v,out var value)){context.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None, 数字过大));goto nextStep;}if (value is not 2 and 8){context.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None, 数字只能在[2,8]));goto nextStep;}maxCount value;}//此处利用标签进行跳转nextStep:var list new Liststring();for (int i 2; i maxCount; i){......//忽略参考06章节}......//忽略参考06章节}
}随便改一下MyTupleMaxTypeArgumentCount.txt里面的内容为非数字类型则会收到