• 学习Source Generators之HelloWorld


    介绍#

    源生成器是 C# 开发人员可以编写的一种新组件,允许执行两个主要操作:

    1. 检索表示正在编译的所有用户代码的编译对象。 可以检查此对象,并且可以编写适用于正在编译的代码的语法和语义模型的代码,就像现在使用分析器一样。
    2. 生成可在编译过程中添加到编译对象的 C# 源文件。 也就是说,在编译代码时,可以提供其他源代码作为编译的输入。

    结合使用这两项操作能充分发挥源生成器的强大功能。 可以使用编译器在编译时构建的丰富元数据检查用户代码。 然后,生成器将 C# 代码发送回基于已分析数据的同一编译。 如果你熟悉 Roslyn 分析器,可以将源生成器视为可发出 C# 源代码的分析器。
    源生成器作为编译阶段运行,如下所示:

    源生成器是由编译器与任何分析器一起加载的 .NET Standard 2.0 程序集。 它在可以加载和运行 .NET Standard 组件的环境中使用。
    注意:目前只能用 .NET Standard 2.0 程序集作源生成器。

    实现Hello Wolrd#

    接下来开始使用Source Genertor实现我们我HelloWorld程序。

    创建项目#

    创建一个HelloWorld的控制台项目。
    将Program改成部分类。并添加一个Hello的部分方法。

    namespace HelloWorld
    {
        partial class Program
        {
            static void Main(string[] args)
            {
                Hello("Generated Code");
            }
    
            static partial void Hello(string name);
        }
    }
    
    

    接下来创建一个netstandard2.0的类库。
    命名成HelloWorld.Analysis。添加依赖Microsoft.CodeAnalysis.CSharp和Microsoft.CodeAnalysis.Analyzers。需要设置PrivateAssets=“all”。
    完整配置如下:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0TargetFramework>
        <LangVersion>latestLangVersion>
      PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
      ItemGroup>
    Project>
    
    

    这里需要注意的是Microsoft.CodeAnalysis.CSharp不宜使用太高版本,太高版本可能会出现无法正常生成代码的情况。
    在HelloWorld项目中添加HelloWorld.Analysis的项目依赖。并设置OutputItemType="Analyzer" ReferenceOutputAssembly="false"
    如下所示:

    "Microsoft.NET.Sdk">
    
    	
    		Exe
    		net8.0
    		enable
    		enable
    	
    
    	
    		"..\HelloWorld.Analysis\HelloWorld.Analysis.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    	
    
    
    

    实现Generator#

    在HelloWorld.Analysis中添加HelloSourceGenerator类。继承并实现ISourceGenerator接口。并且需要在类上加上Generator特性标签。
    然后再Exceute中实现我们的代码生成逻辑。

    using Microsoft.CodeAnalysis;
    
    namespace HelloWorld.Analysis
    {
        [Generator]
        public class HelloSourceGenerator : ISourceGenerator
        {
            public void Execute(GeneratorExecutionContext context)
            {
                var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);
    
                string source = $@"// 
    using System;
    
    namespace {mainMethod.ContainingNamespace.ToDisplayString()}
    {{
        public static partial class {mainMethod.ContainingType.Name}
        {{
            static partial void Hello(string name) =>
                Console.WriteLine($""Hello: '{{name}}'"");
        }}
    }}
    ";
                var typeName = mainMethod.ContainingType.Name;
    
                context.AddSource($"{typeName}.g.cs", source);
            }
    
            public void Initialize(GeneratorInitializationContext context)
            {
            }
        }
    }
    
    

    在上面代码中,通过Compilation获取Program程序入口的信息。包括命名空间,类名等等等。最后AddSource($"{typeName}.g.cs", source);表示我们把代码生成到.g.cs后缀的文件中。

    编译#

    接下来启动编译项目,在HelloWorld的依赖项的分析器中会出现一个Program.g.cs文件。
    image.png
    双击打开可以看到生成的代码。并且会提示该文件是自动生成的,无法编辑。
    可以看到,文件中我们实现了部分类Program中的部分方法Hello。
    image.png

    运行项目#

    启动项目,可以看到我们成功输出由Source Genertor生成的Hello方法的实现。
    image.png

    注意事项#

    细心的同学可能会看到我们编译的时候会出现一个警告:
    warning RS1036: “HelloWorld.Analysis.HelloSourceGenerator”: 包含分析器或源生成器的项目应指定属性“true
    建议我们在项目中添加EnforceExtendedAnalyzerRules的属性。
    当我们添加这个属性后这个警告就会消失。

    <Project Sdk="Microsoft.NET.Sdk">
    
    	<PropertyGroup>
    		<TargetFramework>netstandard2.0TargetFramework>
    		<LangVersion>latestLangVersion>
    		<EnforceExtendedAnalyzerRules>trueEnforceExtendedAnalyzerRules>
    	PropertyGroup>
    
    	<ItemGroup>
    		<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
    		<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
    	ItemGroup>
    Project>
    

    设置 EnforceExtendedAnalyzerRules 为 true 的作用就是提供 API 禁用分析功能,防止写出分析器不支持的代码。设置 EnforceExtendedAnalyzerRules 为 true 时,有部分的 API 将会被提示不可用。具体API可以看: https://raw.githubusercontent.com/dotnet/roslyn-analyzers/2b6ab8d727ce73a78bcbf026ac75ea8a7c804daf/src/Microsoft.CodeAnalysis.Analyzers/Core/AnalyzerBannedSymbols.txt

    Debug#

    前面我们直接编译就生成了代码,打断点也不会触发。那么我们如何调试SourceGenerator呢?
    可以使用Debugger.Launch();来触发调试。
    在我们的运行代码中加入这一行。在编译时会触发调试提示。

    using Microsoft.CodeAnalysis;
    using System.Diagnostics;
    
    namespace HelloWorld.Analysis
    {
        [Generator]
        public class HelloSourceGenerator : ISourceGenerator
        {
            public void Execute(GeneratorExecutionContext context)
            {
                Debugger.Launch(); //触发Debug
                var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);
    
                string source = $@"// 
    using System;
    
    namespace {mainMethod.ContainingNamespace.ToDisplayString()}
    {{
        public static partial class {mainMethod.ContainingType.Name}
        {{
            static partial void Hello(string name) =>
                Console.WriteLine($""Hello: '{{name}}'"");
        }}
    }}
    ";
                var typeName = mainMethod.ContainingType.Name;
    
                context.AddSource($"{typeName}.g.cs", source);
            }
    
            public void Initialize(GeneratorInitializationContext context)
            {
            }
        }
    }
    
    

    加入代码后,重新执行项目编译操作。会弹出Debugger提示。
    image.png
    点击OK即可进入调试模式。
    image.png
    如果不需要点击Cancel则可以跳过。

    结语#

    本文初步的了解了SourceGenerator的功能以及使用和调试的方式,后面的文章我们再来逐步深入学习。
    文章代码仓库地址https://github.com/fanslead/Learn-SourceGenerator

  • 相关阅读:
    论文解读:ToxinPred2:一种预测蛋白质毒性的改进方法
    Serverless Devs 重大更新,基于 Serverless 架构的 CI/CD 框架:Serverless-cd
    qt day 6
    hdoj 3549 Flow Problem 【最大流】
    基于Java+springboot+SSM的医疗报销系统的设计与实现
    uni-app:实现当前时间的获取,并且根据当前时间判断所在时间段为早上,下午还是晚上
    Vue学习笔记之Vue脚手架使用 6.23
    Docker搭建私有镜像仓库及推送、拉取私服镜像
    外观设计模式
    缓存一致性解决方案——改数据时如何保证缓存和数据库中数据的一致性
  • 原文地址:https://www.cnblogs.com/fanshaoO/p/18101185