zoukankan      html  css  js  c++  java
  • SourceGenerator入门指北

    1 SourceGenerator介绍

    SourceGenerator于2020年4月29日在微软的.net blog首次介绍,大概说的是开发者编可以写分析器,在项目代码编译时,分析器分析项目既有的静态代码,允许添加源代码到GeneratorExecutionContext中,一同与既有的代码参与编译。

    2 SourceGenerator未出生时

    在还没有SourceGenerator的时候,开发者要实现AOP框架时,往往使用以下技术:

    • Emit技术,运行时生成代理类型,难点比较低且不用考虑语言的语法,但不适用于需要完全AOT编译的平台。
    • msbulid+代码分析+代码生成,拦截build的某个阶段运行task,task分析既有代码的语法,然后生成代理代码到编译器中。
    • msbuild+Mono.Cecil, 拦截build的某个阶段运行task,task通过Cecil静态修改编译输出的程序集,补充代理IL到程序集中,然后程序集可能会继续参与下一步的AOT编译过程。

    WebApiClient.JIT与WebApiClient.AOT包,分别适用上面的Emit和Cecil,后者难度非常大,且表现得不太稳定。

    3 第一个吃螃蟹的落地项目

    一直比较关心SourceGenerator,现在我觉得,SourceGenerator现在已到达可以使用的阶段了。WebApiClientCore之前有个分支做SourceGenerator的实验,但迟迟没有合并到master来。现在它已经合并到master,并以一个Extensions.SourceGenerator扩展包的方式出现,让WebApiClientCore多一种代理类生成的方式选择。这个扩展包编写时非常简单,我已经不想看以前是怎么用Cecil为程序集插入静态IL的代码了。

    4 如何编写xxxSourceGenerator

    创建一个netstandard2.0的程序集

    <Project Sdk="Microsoft.NET.Sdk">
    
    	<PropertyGroup>
    		<TargetFramework>netstandard2.0</TargetFramework>
    		<LangVersion>8.0</LangVersion>
    		<Nullable>enable</Nullable>
    	</PropertyGroup>
    
    	<ItemGroup>
    		<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
    		<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
    	</ItemGroup>
    
    </Project>
    

    实现ISyntaxReceiver,接收编译时语法树的遍历

    class xxxSyntaxReceiver : ISyntaxReceiver
    {
        /// <summary>
        /// xxx感兴趣的接口列表
        /// </summary>
        private readonly List<InterfaceDeclarationSyntax> interfaceSyntaxList = new List<InterfaceDeclarationSyntax>();
    
        /// <summary>
        /// 访问语法树 
        /// </summary>
        /// <param name="syntaxNode"></param>
        void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode)
        {
            if (syntaxNode is InterfaceDeclarationSyntax syntax)
            {
                this.interfaceSyntaxList.Add(syntax);
            }
        }
    }
    

    实现ISourceGenerator,且使用[Generator]特性

    [Generator]
    public class xxxSourceGenerator : ISourceGenerator
    {
        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="context"></param>
        public void Initialize(GeneratorInitializationContext context)
        {
            context.RegisterForSyntaxNotifications(() => new xxxSyntaxReceiver());
        }
    
        /// <summary>
        /// 执行
        /// </summary>
        /// <param name="context"></param>
        public void Execute(GeneratorExecutionContext context)
        {
            if (context.SyntaxReceiver is xxxSyntaxReceiver receiver)
            {
                // 从receiver获取你感兴趣的语法节点
                // 然后拼接成string的代码
                // 把代码添加到context
                context.AddSource("代码1的id","这里是c#代码,会参与编译的");
            }
        }
    }
    

    5 如何调试xxxSourceGenerator

    在被调试项目以分析器方式引入xxxSourceGenerator项目

    <ItemGroup>
    	<ProjectReference Include="..xxxSourceGeneratorxxxSourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    </ItemGroup>
    

    在xxxSourceGenerator里加入Debugger.Launch()

    没错,这是最简单的触发调试方式,你在xxxSourceGenerator入口加这么一行代码,被调试的项目只要一编译,vs就弹出且断点到Debugger.Launch()这行,然后就可以一步一步执行调试了。

    6 如何打包发布xxxSourceGenerator

    SourceGenerator项目本质上还是分析器项目,所以可以打包成一个nuget包,别的项目引用这个nuget包之后,就自动以分析器的方式安装到目标项目中,然后激活了你的xxxSourceGenerator。

    分析器的nuget打包

    • 需要将编译出的xxxSourceGenerator.dll放到nuget包的analyzersdotnetcs目录下
    • 需要在nuget包的tools目录下放置分析器安装和卸载脚本install.ps1和uninstall.ps1,这脚本是通用的。

    install.ps1

    param($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)
                }
            }
        }
    }
    

    uninstall.ps1

    param($installPath, $toolsPath, $package, $project)
    
    $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
    
    foreach($analyzersPath in $analyzersPaths)
    {
        # Uninstall the language agnostic analyzers.
        if (Test-Path $analyzersPath)
        {
            foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
            {
                if($project.Object.AnalyzerReferences)
                {
                    $project.Object.AnalyzerReferences.Remove($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)
    {
        # Uninstall language specific analyzers.
        $languageAnalyzersPath = join-path $analyzersPath $languageFolder
        if (Test-Path $languageAnalyzersPath)
        {
            foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
            {
                if($project.Object.AnalyzerReferences)
                {
                    try
                    {
                        $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
                    }
                    catch
                    {
    
                    }
                }
            }
        }
    }
    

    7 结束语

    本文讲的SourceGenerator和语法分析器,如果你感兴趣但在实验中遇到困难,你可以下载WebApiClient的源代码来直接体验和调试,然后依葫芦画瓢造自己的SourceGenerator。

  • 相关阅读:
    【数论】错排问题
    【数论】求逆元的几种方式
    【数论】卢卡斯定理模板 洛谷P3807
    【单调队列优化dp】 分组
    【期望dp】绵羊跳弹簧
    软件工程总结
    结对项目-地铁出行路线规划程序(续)
    个人作业--week3
    个人作业-week2
    个人作业-week1
  • 原文地址:https://www.cnblogs.com/kewei/p/14322474.html
Copyright © 2011-2022 走看看