• C#源代码生成器深入讲解一


    C#源代码生成器

    01 源代码生成器初体验

    1. 新建一个类库,一定是standard2.0版本,否则会出问题。
    2. 引用Nuget包Microsoft.CodeAnalysis.Common
    3. 新建一个类,继承自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",
    
               $$"""
                //加上这句话,告知编译器,这个文件是由源代码生成器生成的,
                //防止编译器进行代码分析,避免不必要的编译器警告
                //
                namespace 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)
        {
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    注意事项

    • 在使用某些方法或者特性时,最好写全命名空间,并用global::命名空间限定符
    • 文件名称建议使用.g.cs后缀
    • 建议在开头增加注释
    • 可以使用原生字符串符号"""三个双引号,以及双内插符号$$,这样可以使用{{}}来进行内插
    1. 建立一个控制台项目,并引用刚才的类库,要加上OutputItemType和 ReferenceOutAssembly

      <ItemGroup>
       
        <ProjectReference Include="..\SourceGeneratorConsole.Generator\SourceGeneratorConsole.Generator.csproj" OutputItemType="Analyzer" ReferenceOutAssembly="false" />
      ItemGroup>
      
      • 1
      • 2
      • 3
      • 4
    2. 使用源代码生成器,使用生成器中所生成的方法。

    using GreetingTest;
    Greeting.SayHello("李四");
    
    • 1
    • 2

    02 使用分部类型

    很多时候,不需要源代码生成器生成完整的类型,而是和主程序交互,分别形成一定的代码,此时可以使用分部类型来实现。

    1. 在上一章节中的控制台项目中增加一个类
    namespace GreetingTest
    {
        public static partial class GreetingUsePartialClass
        {
            public static partial void SayHello(string name);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 修改上一章节中源代码生成器类库项目
    namespace SourceGeneratorConsole.Generator;
    
    [Generator(LanguageNames.CSharp)]
    public sealed class GreetingGenerator : ISourceGenerator
    {
        //源代码生成器的所要生成的方法
        public void Execute(GeneratorExecutionContext context)
        {
            //修改为GreetingUsePartialClass.g.cs,和控制台中定义的名称相对应
            context.AddSource("GreetingUsePartialClass.g.cs",
    
               $$"""
                //
                namespace 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)
        {
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    1. 在控制台应用中调用
    static void Main(string[] args)
    {
        GreetingUsePartialClass.SayHello("Source Generator");
        Console.Read();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    03 使用SyntaxReceiver属性

    上一章节中,在源代码生成器中将类名和方法名写进去了,源代码生成器往往是应用在不同的项目中,类型名和方法名都不是固定的,所以要动态的修改名称,这就要用到了SyntaxContextReceiver属性。

    1. 在上一章节中的源代码生成器文件中,写一个SyntaxReceiver
    //file只在本文件可以用,跟internal一样是访问修饰符
    //提供一个语法搜索类型,这个类型只用于寻找主要项目里的指定语法满足条件部分
    file sealed class SyntaxReceiver:ISyntaxReceiver
    {
        //表示一个方法的语法节点,这个方法就是用到的SayHello方法,这个方法的返回值是void,静态、partial
        public 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;
            }
            //判断子节点,也就是类型内部的成员是否有partial
            foreach (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;
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    1. 修改属性生成器
    [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().OfType<TypeDeclarationSyntax>().First();
            var typeName = type.Identifier.ValueText;
    
            //建议名称使用.g.cs
            //建议使用全局命名空间global::  为了防止诸如System和Windows.System冲突
            context.AddSource($"{typeName}.g.cs",
    
               $$"""
                //加上这句话,告知编译器,这个文件是由源代码生成器生成的,
                //防止编译器进行代码分析,避免不必要的编译器警告
                //
                namespace 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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在Initialize中返回刚才创建的类, Execute方法中获得相应的类名称。

    1. 调用
    static void Main(string[] args)
    {
        GreetingUsePartialClass.SayHello("Source Generator");
        Console.Read();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20231110102144123

    04 调试源代码生成器

    源代码生成器是在编译阶段中自动生成,一般无法调试,这时可以在源代码生成器中的Initialize方法中加上

    //添加调试器,如果程序没有调试器的时候就启动
    //如果用了多个源代码生成器,只要有一个配置了这个,也可以调试其他的
    //if (!Debugger.IsAttached)
    //{
    //    Debugger.Launch();
    //}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    05 ISyntaxContextReceiver属性

    上面是已知有了SayHello的方法,假设不知道是什么方法名,如何使用源代码生成器,本节借助特性来实现

    1. 在主项目中声明特性,一般都是放在主项目中,因为在主项目中的引用其他项目的设置中已设置了OutputItemType="Analyzer" ReferenceOutAssembly="false",这表示不会将生成器的作为引用,而是将分析器生成的代码,如果将特性定义在生成器中,主项目引用不到特性定义
    namespace SourceGeneratorConsole
    {
        [AttributeUsage(AttributeTargets.Method,AllowMultiple =false,Inherited =false)]
        public sealed class SayHelloAttribute:Attribute; //新语法,特性可以直接使用分号结束
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 在主项目中声明一个分部方法
    namespace SourceGeneratorConsole
    {
       public partial class GreetingUseAttribute
        {
            [SayHello]
            public static partial void SayHi(string name);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 按照上面的流程创建源代码生成器
    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;
                    //获取完整命名空间名称,包括global
                    var 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", $$"""
                        //加上这句话,告知编译器,这个文件是由源代码生成器生成的,
                        //防止编译器进行代码分析,避免不必要的编译器警告
                        //
                        namespace {{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 List<IMethodSymbol> 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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    1. 使用源代码生成器
    GreetingUseAttribute.SayHi("使用特性的属性生成器");
    
    • 1

    image-20231110102223664

    06 自定义MyTuble类型实战

    我们经常用到Func泛型委托,该泛型委托最多支持16个参数和一个返回值,因为泛型定义没有类似于可变参数的功能,对于不同数量的泛型参数一定要定义同数量的泛型定义。类似于下面这样。

    Func<TResult>
    Func<T, TResult>
    Func<T1, T2, TResult>
    Func<T1, T2, T3, TResult>
    Func<T1, T2, T3, T4, TResult>
    Func<T1, T2, T3, T4, T5, TResult>
    Func<T1, T2, T3, T4, T5, T6, TResult>
    Func<T1, T2, T3, T4, T5, T6, T7, TResult>
    Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们仿照Func泛型委托自定义一个MyTuple泛型类型

    1. 先定义一个MyTuple模板,这是一个定义了2个泛型参数的MyTuple类型,根据该模板要定义支持多个泛型参数的MyTuple类型
    public readonly struct MyTuple<T1, T2>(T1 value1, T2 value2) : 
        IEqualityOperators<MyTuple<T1, T2>, MyTuple<T1, T2>, bool> 
        where T1 : IEqualityOperators<T1, T1, bool> 
        where T2 : IEqualityOperators<T2, T2, bool>
    {
        public T1 Value1 { get; } = value1;
        public T2 Value2 { get; } = value2;
    
        public static bool operator ==(MyTuple<T1, T2> left, MyTuple<T1, T2> right)
        {
            return left.Value1 == right.Value1 && left.Value2 == right.Value2;
        }
        public static bool operator !=(MyTuple<T1, T2> left, MyTuple<T1, T2> right)
        {
            return !(left == right);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 写一个源代码生成器,根据上面的模板进行改造,自动生成含有1-8个泛型参数的MyTuple类型,其根本原理就是字符串的操作。
    [Generator(LanguageNames.CSharp)]
    public class MyTupleGenerator : ISourceGenerator
    {
        public void Execute(GeneratorExecutionContext context)
        {
            var list = new List<string>();
            for (int i = 2; i <= 8; i++)
            {
                var indices = Enumerable.Range(1, i).ToArray();
                var genericArgs = $"<{string.Join(", ", 
                    from index in indices
                    select $"T{index}" )}>";
                var ctorArgs = string.Join(", ", 
                    from index in indices
                    select $"T{index} value{index}");
                var constraints = string.Join("\r\n\t",
                    from index in indices
                    select $"where T{index}: global::System.Numerics.IEqualityOperators{index},T{index},bool>");
                var properties = string.Join("\r\n\t",
                    from index in indices
                    select $"public T{index} Value{index} {{ get; }}=value{index};");
    
                var comparison = string.Join(" && ", from index in indices
                                                     select $"left.Value{index} == right.Value{index}");
    
                list.Add($$"""
                    public readonly struct MyTuple{{genericArgs}}({{ctorArgs}}):
                    global::System.Numerics.IEqualityOperators<MyTuple{{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", $$"""
                //
                namespace System;
                {{string.Join("\r\n\r\n",list)}}
                """);
        }
    
        public void Initialize(GeneratorInitializationContext context)
        {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    1. 主项目引用源代码生成器后,使用MyTuple
    var myTuple1 = new MyTuple<int, double>(1, 3.0);
    var myTuple2 = new MyTuple<int, double>(1, 3.0);
    var myTuple3 = new MyTuple<int, double,float>(1, 3.0,5.6f);
    var myTuple4 = new MyTuple<int, double,float>(1, 3.0,5.6f);
    var myTuple5 = new MyTuple<int, double,float,uint>(1, 3.0,5.6f,8);
    var myTuple6 = new MyTuple<int, double,float,uint>(1, 3.0,5.6f,7);
    
    Console.WriteLine(myTuple2 == myTuple1);
    Console.WriteLine(myTuple4 == myTuple3);
    Console.WriteLine(myTuple6 == myTuple5);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    image-20231110102032761

    07AdditionalFiles的使用

    上一章节中,我们在直接定义了MyTuple时设置最大泛型参数数量为8,如果我们需要根据需要来设置最大泛型参数数量,则可以在主项目中增加一个配置文件,文件中对此进行设置,并在源代码生成器中使用GeneratorExecutionContext的AdditionalFiles属性来处理非代码文件

    1. 在主项目中增加一个文件,本次案例增加一个MyTupleMaxTypeArgumentCount.txt文件,在该文件中写入4。
    2. 在主项目配置中,增加
    <ItemGroup>
    	<AdditionalFiles Include="MyTupleMaxTypeArgumentCount.txt"/>
    ItemGroup>
    
    • 1
    • 2
    • 3
    1. 在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 List<string>();
            for (int i = 2; i <= maxCount; i++)
            {
                ......//忽略,参考06章节
            }
            ......//忽略,参考06章节
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    08自定义编译器诊断信息

    在进行编译时,编译器会自动给出编译信息供用户查看,通常编译器诊断信息如下所示。

    由于源代码生成器会自动后台生成,所以给出诊断信息是十分必要的。本章节根据07章节中的章节,给出自定义编译器诊断信息的

    [Generator(LanguageNames.CSharp)]
    public class MyTupleGenerator : ISourceGenerator
    {
        //首先创建一个DiagnosticDescriptor
        static 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 List<string>();
            for (int i = 2; i <= maxCount; i++)
            {......//忽略,参考06章节
            }
            ......//忽略,参考06章节
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    随便改一下MyTupleMaxTypeArgumentCount.txt里面的内容为非数字类型,则会收到

    image-20231110153622944

  • 相关阅读:
    springboot+avue框架开发的医院绩效考核系统全套源码
    ISCSLP 2022 | AccentSpeech—从众包数据中学习口音来构建目标说话人的口音语音合成系统
    PNG怎么转换成PDF?这篇文章教会你
    Java项目:汽车租车管理系统(java+SSM+HTML+JSP+bootstrap+layui+Mysql)
    电脑重装系统后Win11安全中心无法打开如何解决
    Python学习(4)-基础语法(模块)
    栈的概念及用法
    sql小技巧:日期区间和格式化操作
    milvus upsert流程源码分析
    srpingboot security demo
  • 原文地址:https://blog.csdn.net/weixin_44064908/article/details/134327041