IIncrementalGenerator 增量 Source Generator 生成代码入门 从语法到语义 获取类型完全限定名
本文告诉大家如何在使用 IIncrementalGenerator 进行增量的 Source Generator 生成代码时,如何从语法分析过程,将获取的语法 Token 转换到语义分析上,比如获取类型完全限定名。一个使用的例子是在拿到一个 Token 表示某个类型时,本文将演示通过语义分析获取到拿到的 Token 的 Type 类型的 FullName 带命名空间的完全限定名
本文属于 IIncrementalGenerator 增量 Source Generator 生成代码入门系列博客,前置的博客是 尝试 IIncrementalGenerator 进行增量 Source Generator 生成代码
更多编译器、代码分析、代码生成相关博客,请参阅我的 博客导航
在开始之前,期望大家已了解语法分析和语义分析的差别。可通过阅读 Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码(语法分析) - walterlv 和 使用 Roslyn 对 C# 代码进行语义分析 - walterlv 博客对此进行了解
初始化项目
在开始之前,先创建好测试使用的项目,创建两个项目,分别是分析器项目,和使用分析器的项目。项目分别是 WarnaijakeCiwhelwajifaje.Analyzers 和 WarnaijakeCiwhelwajifaje 两个项目
在 WarnaijakeCiwhelwajifaje.Analyzers 的 csproj 项目文件里面的代码大概如下
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" PrivateAssets="all" />
</ItemGroup>
</Project>
在 WarnaijakeCiwhelwajifaje 里引用分析器,项目文件里面的代码大概如下
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\WarnaijakeCiwhelwajifaje.Analyzers\WarnaijakeCiwhelwajifaje.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
</Project>
在 WarnaijakeCiwhelwajifaje 里添加 Program.cs 文件,在此文件放入 Main 方法,代码如下
namespace WarnaijakeCiwhelwajifaje;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
本文将演示通过语法分析拿到了 Program 定义,接着通过语义分析,拿到 Program 的完全限定名,也就是 global::WarnaijakeCiwhelwajifaje.Program 内容
创建分析器
接下来将在新建的分析器代码里面,先通过语法分析快速获取到 Program 的代码定义,接着在 SemanticModel 里面获取到 Program 类型的完全限定名
先新建继承 IIncrementalGenerator 的分析器类型,代码大概如下
[Generator(LanguageNames.CSharp)]
public class FooIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
... // 忽略代码
}
}
搭建解析框架
为了能够让代码调试跑起来,咱开始在 FooIncrementalGenerator 的 Initialize 方法里面编写一些调试辅助代码和搭建基础的框架代码
先加上 Debugger.Launch() 用于启动调试,加上这个代码即可在 WarnaijakeCiwhelwajifaje 项目构建时触发调试异常,从而可以被 VisualStudio 附加
[Generator(LanguageNames.CSharp)]
public class FooIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
Debugger.Launch();
... // 忽略代码
}
}
接着通过 IncrementalGeneratorInitializationContext 的 SyntaxProvider 属性,调用 CreateSyntaxProvider 方法创建处理逻辑
此 CreateSyntaxProvider 方法接受两个委托函数,以下将告诉大家这两个委托函数参数的作用
通过 CreateSyntaxProvider 即可创建对所有参与构建的 cs 文件里面的代码逻辑的处理,在这里的思想是每次变更都是一个个进来的,变更的文件进来之后,将会先进入 CreateSyntaxProvider 方法传入的第一个委托参数,在这个委托参数里面将用来快速的语法判断,判断当前变更的文件是否在此业务逻辑上是感兴趣的。通过此快速判断逻辑即可过滤掉不需要处理的信息,从而减少后续需要处理的工作量,提升性能。在搭建的框架代码里面,只需要每次都返回 true 表示全部都需要处理即可
第二个委托参数就是转换处理逻辑,传入委托的是 GeneratorSyntaxContext 参数,要求返回值是处理完成返回的任意类型。在 GeneratorSyntaxContext 类型参数里面将包括语法的 Node 属性,和包括语义的 SemanticModel 属性,在框架代码里面只需要每次都返回 GeneratorSyntaxContext 参数即可
代码如下
var incrementalValuesProvider = context.SyntaxProvider.CreateSyntaxProvider((syntaxNode, _) =>
{
return true;
},
(GeneratorSyntaxContext generatorSyntaxContext, CancellationToken _) =>
{
return generatorSyntaxContext;
});
如果只有以上的代码,大家将会发现在委托里面添加断点将不会进入,这是因为 incrementalValuesProvider 没有被任何地方使用。在 Roslyn 里面的设计就是缓加载方式,和 Linq 一样,只有在需要用到的时候才执行
为了让以上的委托能够被执行,添加 RegisterSourceOutput 用来让底层执行委托内容,代码如下
context.RegisterSourceOutput(incrementalValuesProvider, (sourceProductionContext, generatorSyntaxContext) =>
{
// 加上这里只是为了让 incrementalValuesProvider 能够执行
});
添加完成框架之后的代码如下
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace WarnaijakeCiwhelwajifaje.Analyzers
{
[Generator(LanguageNames.CSharp)]
public class FooIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
Debugger.Launch();
var incrementalValuesProvider = context.SyntaxProvider.CreateSyntaxProvider((syntaxNode, _) =>
{
return true;
},
(GeneratorSyntaxContext generatorSyntaxContext, CancellationToken _) =>
{
return generatorSyntaxContext;
});
context.RegisterSourceOutput(incrementalValuesProvider, (sourceProductionContext, generatorSyntaxContext) =>
{
// 加上这里只是为了让 incrementalValuesProvider 能够执行
});
}
}
}
接下来就可以开始修改框架的代码,逐个换成演示的代码
语法过滤
回到咱演示的主题,获取到 Program 代码对应的类型的完全限定名。从这个需求可以知道,咱感兴趣的语法一定是一个 class 类型定义,如此可以在 CreateSyntaxProvider 的第一个委托里面进行快速的语法过滤,过滤只有 ClassDeclaration 才允许进入后续逻辑,代码如下
var incrementalValuesProvider = context.SyntaxProvider.CreateSyntaxProvider((syntaxNode, _) =>
{
// 在此进行快速的语法判断逻辑,可以判断当前的内容是否感兴趣,如此过滤掉一些内容,从而减少后续处理,提升性能
// 这里样式的是获取到 Program 类的完全限定名,也就是只需要用到 Class 类型
return syntaxNode.IsKind(SyntaxKind.ClassDeclaration);
},
(GeneratorSyntaxContext generatorSyntaxContext, CancellationToken _) =>
{
return generatorSyntaxContext;
});
从语法分析到语义分析
在 CreateSyntaxProvider 的第二个委托参数里面将用来编写处理逻辑,输入的参数 GeneratorSyntaxContext 类型里面包含了两个属性,分别是包括语法的 Node 属性,和包括语义的 SemanticModel 属性
先通过语法获取到 Program 类型定义,代码如下
// 从这里可以获取到语法内容
if (generatorSyntaxContext.Node is ClassDeclarationSyntax classDeclarationSyntax)
{
}
else
{
// 理论上不会进入此分支,因为在之前判断了类型
}
这里的 Node 属性一定是 ClassDeclarationSyntax 类型,这是因为在前面语法部分限制了 IsKind(SyntaxKind.ClassDeclaration) 决定这里一定是类型定义
使用 SemanticModel 属性从语法 ClassDeclarationSyntax 获取到语义,代码如下
var symbolInfo = generatorSyntaxContext.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax)!;
如此即可完成从语法分析到语义分析。根据 使用 Roslyn 对 C# 代码进行语义分析 - walterlv 博客的示例,可以了解到拿到 symbolInfo 对象之后,即可获取到当前语法 Program 对应的类型,约等于拿到反射的 Type 类型,即可方便获取到对应的命名空间,继承的类型,包含的成员等等
获取类型名
获取类型名的方法可以是让 symbolInfo 进行格式化输出,格式化输出可以定制输出格式,如以下代码
// 带上 global 格式的输出 FullName 内容
var symbolDisplayFormat = new SymbolDisplayFormat
(
// 带上命名空间和类型名
SymbolDisplayGlobalNamespaceStyle.Included,
// 命名空间之前加上 global 防止冲突
SymbolDisplayTypeQualificationStyle
.NameAndContainingTypesAndNamespaces
);
调用 ToDisplayString 即可进行格式化输出定义
var displayString = symbolInfo.ToDisplayString(symbolDisplayFormat);
通过以上代码就能拿到 global::WarnaijakeCiwhelwajifaje.Program 字符串
以下是分析器的代码
[Generator(LanguageNames.CSharp)]
public class FooIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
Debugger.Launch();
var incrementalValuesProvider = context.SyntaxProvider.CreateSyntaxProvider((syntaxNode, _) =>
{
// 在此进行快速的语法判断逻辑,可以判断当前的内容是否感兴趣,如此过滤掉一些内容,从而减少后续处理,提升性能
// 这里样式的是获取到 Program 类的完全限定名,也就是只需要用到 Class 类型
return syntaxNode.IsKind(SyntaxKind.ClassDeclaration);
},
(generatorSyntaxContext, _) =>
{
// 从这里可以获取到语法内容
if (generatorSyntaxContext.Node is ClassDeclarationSyntax classDeclarationSyntax)
{
var symbolInfo = generatorSyntaxContext.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax)!;
// 带上 global 格式的输出 FullName 内容
var symbolDisplayFormat = new SymbolDisplayFormat
(
// 带上命名空间和类型名
SymbolDisplayGlobalNamespaceStyle.Included,
// 命名空间之前加上 global 防止冲突
SymbolDisplayTypeQualificationStyle
.NameAndContainingTypesAndNamespaces
);
var displayString = symbolInfo.ToDisplayString(symbolDisplayFormat);
}
else
{
// 理论上不会进入此分支,因为在之前判断了类型
}
return generatorSyntaxContext;
});
context.RegisterSourceOutput(incrementalValuesProvider, (sourceProductionContext, generatorSyntaxContext) =>
{
// 加上这里只是为了让 incrementalValuesProvider 能够执行
});
}
}
源代码
本文所有代码放在 github 和 gitee 上,可以通过以下方式获取整个项目的代码
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin d1197778d4a96524de210e44a662331e7340a720
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin d1197778d4a96524de210e44a662331e7340a720
获取代码之后,进入 WarnaijakeCiwhelwajifaje 文件夹
IIncrementalGenerator 增量 Source Generator 生成代码入门 从语法到语义 获取类型完全限定名的更多相关文章
- mybatis Generator生成代码及使用方式
本文原创,转载请注明:http://www.cnblogs.com/fengzheng/p/5889312.html 为什么要有mybatis mybatis 是一个 Java 的 ORM 框架,OR ...
- mybatis之generator生成代码
首先在pom文件中引入以下代码 <plugin> <groupId>org.mybatis.generator</groupId> <artifactId&g ...
- 用org.mybatis.generator 生成代码
1:引入pom 2:增加生成配置xml: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ...
- 2016.7.12 eclispe使用mybatis generator生成代码时提示project E is not exist
运行mybatis-generator之后,出现错误:project E is not exist 错误原因:使用了项目的绝对路径. http://bbs.csdn.net/topics/3914 ...
- mybatis Generator生成代码及使用方式(转载)
转载自:http://www.cnblogs.com/fengzheng/p/5889312.html 文章很棒,很不错,转了.
- 把Mybatis Generator生成的代码加上想要的注释
作者:王建乐 1 前言 在日常开发工作中,我们经常用Mybatis Generator根据表结构生成对应的实体类和Mapper文件.但是Mybatis Generator默认生成的代码中,注释并不是我 ...
- IDEA使用mybatis generator自动生成代码
主要就三步: 1.pom 文件中引入jar包并配置 build 属性 <dependencies> <!-- 自动生产mapper Begin! --> <depende ...
- springboot 使用mybatis-generator自动生成代码
这里只介绍mybatis generator生成代码 一.pom配置 在build-->plugins-->添加plugin <plugin> <groupId>o ...
- SpringBoot入门篇--整合mybatis+generator自动生成代码+druid连接池+PageHelper分页插件
原文链接 我们这一篇博客讲的是如何整合Springboot和Mybatis框架,然后使用generator自动生成mapper,pojo等文件.然后再使用阿里巴巴提供的开源连接池druid,这个连接池 ...
- 安卓自动生成代码插件-Android code Generator(转)
编辑推荐:稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识.前端.后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过! 介绍 A ...
随机推荐
- 记录--这个前端Api管理方案会更好?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 简介 大家好,前端小白一枚,目前接触后台管理系统比较多,经常遇到不同对象的增删改查的接口,如何对Api进行一个有比较好的管理是个问题.在学 ...
- 记录--前端路由 hash 与 history 差异
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 简单介绍 Vue Router Vue Router 是 Vue.js 官方的路由插件,它和 Vue.js 是深度集成的,适合用于构建单页 ...
- 【Spring注解驱动开发】你敢信?面试官竟然让我现场搭建一个AOP测试环境!
写在前面 今天是9月1号,金九银十的跳槽黄金期已拉开序幕,相信很多小伙伴也在摩拳擦掌,想换一个新的工作环境.然而,由于今年疫情的影响,很多企业对于招聘的要求是越来越严格.之前,很多不被问及的知识点,最 ...
- 镭速Raysync v6.6.8.0版本发布
最近镭速发布了v6.6.8.0版本,已经发布上线了.主要更新内容有服务器下发任务支持指定客户端,客户端增加日志清理和日志压缩,自动删除源文件保持源目录结构,支持将文件投递给其他成员等功能,详细的更新内 ...
- 给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
private static void stringSubLen(String msg) { int max = 0; int left = 0; Map<Character,Integer&g ...
- KingbaseES 语句like前匹配如何使用索引
前言 有现场同事反馈 sql语句 like 使用后缀通配符 % 不走索引. 至于执行计划没走索引的原因与KingbaseES数据库中的排序规则相关. 测试 测试环境: KingbaseESV8R6C7 ...
- KingbaseES V8R6 常用的系统函数
查看当前日志文件lsn位置: select sys_current_wal_lsn(); 查看某个lsn对应的日志名: select sys_walfile_name('0/1162FBA0'); 查 ...
- Java日期、字符串、毫秒值格式转换
1 /** 2 * 3 */ 4 package study.reliable; 5 /** 6 * @author : Administrator 7 * @date :2022年4月21日 下午9 ...
- Vue入门笔记二
<Vue.js项目实战> 开发所需的包称为开发依赖,应该使用--save-dev标志进行安装 应用运行需要的直接依赖应该使用--save标志进行安装 模板 使用Pug Pug(以前称为Ja ...
- #基数排序#CF1654F Minimal String Xoration
题目传送门 分析 有没有一种办法可以将每个 \(j\) 的比较过程同时进行, 可以发现其实这个过程很像后缀排序,实际上只是加号变成了异或, 从低位到高位重新将字符串排名,用同样的方法做到 \(O(2^ ...