当前位置: 首页 > news >正文

用代码写代码:使用Roslyn API构建语法树并应用于源生成器

在上文构建源生成器的过程中,我们使用字符串直接插入代码。这样做固然方便快捷,但字符串需要手动格式化,且无法检测拼写错误,这对需要生成复杂结构的源生成器项目很不友好。

本文将介绍生成代码的另一种方式:使用Roslyn API构建语法树。

什么是语法树 (Syntax Tree)?

语法树是编译器用于理解C#程序的数据结构。Roslyn在解析C#代码后就会生成一棵语法树,以供后续的进一步分析和编译。

一棵语法树由Node(节点)Token(标记)Trivia(额外信息)构成。

  • SyntaxNode:声明、语句、子句和表达式等语法构造

    如一个类的声明会被解析成一个ClassDeclaration

  • SyntaxToken:独立的关键字、标识符、运算符或标点

    如一个左括号(会被解析为OpenParenToken

  • SyntaxTrivia:不重要的信息,例如标记、预处理指令和注释之间的空格

    如一行注释会被解析为SingleLineCommentTrivia

例如,有如下代码:

using System;
namespace App
{internal class Program{static void Main(string[] args){Console.WriteLine("Hello, World!");}}
}

Roslyn会将这段代码解析为如下结构(此处仅保留SyntaxNode):

CompilationUnitSyntax├── UsingDirectiveSyntax (using System;)└── NamespaceDeclarationSyntax (namespace ConsoleApp1)└── ClassDeclarationSyntax (internal class Program)└── MethodDeclarationSyntax (static void Main(string[] args))└── BlockSyntax└── ExpressionStatementSyntax (Console.WriteLine("Hello, World!");)└── InvocationExpressionSyntax├── SimpleMemberAccessExpressionSyntax (Console.WriteLine)│    ├── IdentifierNameSyntax (Console)│    └── IdentifierNameSyntax (WriteLine)└── ArgumentListSyntax└── ArgumentSyntax└── LiteralExpressionSyntax ("Hello, World!")

我们还可以安装语法树可视化工具(VS Installer>找到对应版本>修改>单个组件>DGML 编辑器)

安装完成后,搜索"Syntax Visualizer "即可

具体可查看使用 Visual Studio 中的 Roslyn 语法可视化工具浏览代码

既然Roslyn需要将代码解析成语法树,那么我们是否可以自行构建一个语法树并"反向"输出C#代码呢?

答案是:可以!

构建语法树

在开始之前,我们需要引入Microsoft.CodeAnalysis.CSharp

若我们需要编写的代码如下:

using System;
namespace ConsoleApp1;public class HelloWorld
{public static void SayHello(){Console.WriteLine("Hello, World!");}
}

创建一个CompilationUnit并添加using语句:

using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;var complicationUnit = CompilationUnit().AddUsings(UsingDirective(IdentifierName("System")));

添加命名空间:

AddMembers(FileScopedNamespaceDeclaration(IdentifierName("ConsoleApp1")));

添加HelloWorld类:

AddMembers(ClassDeclaration("HelloWorld").AddModifiers(Token(SyntaxKind.PublicKeyword)).AddMembers(/*这里编写SayHello方法*/));

编写SayHello方法:

MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)),Identifier("SayHello")).WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword),Token(SyntaxKind.StaticKeyword))).WithBody(Block(ExpressionStatement(InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleAssignmentExpression,IdentifierName("Console"),IdentifierName("WriteLine"))).WithArgumentList(ArgumentList(SingletonSeparatedList<ArgumentSyntax>(Argument(LiteralExpression(SyntaxKind.StringLiteralExpression,Literal("Hello World")))))))))

构建语法树:

var syntaxTree = SyntaxTree(complicationUnit);

全部代码:

using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;var complicationUnit = CompilationUnit().AddUsings(UsingDirective(IdentifierName("System"))).AddMembers(FileScopedNamespaceDeclaration(IdentifierName("ConsoleApp1"))).AddMembers(ClassDeclaration("HelloWorld").AddModifiers(Token(SyntaxKind.PublicKeyword)).AddMembers(MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)),Identifier("SayHello")).WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword),Token(SyntaxKind.StaticKeyword))).WithBody(Block(ExpressionStatement(InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,IdentifierName("Console"),IdentifierName("WriteLine"))).WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(LiteralExpression(SyntaxKind.StringLiteralExpression,Literal("Hello World")))))))))));
var syntaxTree = SyntaxTree(complicationUnit);

使用Roslyn Quoter工具可以将代码直接转化为上文的形式(会比上文写的更长),可作为一些参考

将语法树转换为C#代码

syntaxTree调用GetText()后调用ToString()即可得字符串

var code = syntaxTree.GetText().ToString();
//使用异步重载
//var code = (await syntaxTree.GetTextAsync()).ToString();

或创建一个StreamWriter,将complicationUnit写入即可

await using var streamWriter = new StreamWriter("output.txt");
complicationUnit.WriteTo(streamWriter);

打开输出,我们发现这些代码并没有被格式化——我们需要添加必要的Trivia

usingSystem;namespaceConsoleApp1;publicclassHelloWorld{publicstaticvoidSayHello(){Console.WriteLine("Hello World");}}

我们可以在需要空格或换行的代码后调用WithLeadingTrivia()方法手动添加,但这样会显得代码异常冗长且可读性不佳

更好的方法是给complicationUnit调用NormalizeWhitespace()方法自动添加所需的Trivia

complicationUnit = complicationUnit.NormalizeWhitespace();
var syntaxTree = SyntaxTree(complicationUnit);
var code = (await syntaxTree.GetTextAsync()).ToString();

在源生成器中的应用

在构建SyntaxTree时,手动指定编码形式为UTF8,即可将语法树转换后的代码供源生成器使用

context.RegisterPostInitializationOutput(ctx => ctx.AddSource("HelloWorldSyntaxTree.g.cs",SyntaxTree(complicationUnit, encoding:Encoding.UTF8).GetText()));

源代码

在源生成器中的应用

https://github.com/zxbmmmmmmmmm/SourceGeneratorDemo/blob/master/SourceGeneratorDemo.Generator/SyntaxTreeGenerator.cs

引用

使用代码编写代码 ——Roslyn API 入门

使用 Visual Studio 中的 Roslyn 语法可视化工具浏览代码

Roslyn Quoter

http://www.sczhlp.com/news/37876/

相关文章:

  • 【进阶数论】模乘逆元
  • 为什么用wp做网站可以推广的平台
  • 遵义网上制作网站宽带推广方案
  • 陕西省住房和建设委员会网站seo网站怎么优化
  • wordpress修改首页地址刷关键词优化排名
  • Oracle执行计划解析详解
  • 平台网站建设的公司网络推广的方法你知道几个?
  • vs 2010 网站建设免费优化推广网站的软件
  • 深圳网站设计首选灵点网络靠谱产品宣传
  • 如何做一个购物网站王通seo
  • 织梦如何做网站留言功能百度竞价推广方案的制定
  • 微信小程序saas平台优化大师怎么删除学生
  • 慢查询怎么解决
  • AWR报告和ASH报告详解
  • 怎么自己免费做网站市场调研报告范文大全
  • 网站建设收费标准如何如何申请百度竞价排名
  • 局域网网站域名怎么做如何优化网络
  • 淮南做网站的公司什么是网络营销?
  • 最好的模板网站外贸seo软件
  • wordpress网站建设教程网络营销的基本内容有哪些
  • 委托 网站开发 进什么费用搜狗站长工具
  • 网站成功案例发帖平台
  • 青岛网站制作企业怎么发外链
  • 怎么做卖卷网站标题优化怎么做
  • 字母异位词分组
  • 机器人3D仿真新平台MuJoCo技术解析
  • keil5安装5.43版本时安装pack芯片包卡在下载界面
  • Luogu P9169 [省选联考 2023] 过河卒 题解 [ 紫 ] [ 博弈论 ] [ BFS ] [ 模拟 ] [ 卡常 ]
  • 厦门网站建设方案咨询2022近期时事热点素材
  • 青岛网站建设培训关键词优化平台有哪些