Roslyn入门(一)-C#语法分析
演示环境
简介
今天,Visual Basic和C#编译器是黑盒子:输入文本然后输出字节,编译管道的中间阶段没有透明性。使用.NET编译器平台(以前称为“Roslyn”),工具和开发人员可以利用编译器使用的完全相同的数据结构和算法来分析和理解代码。 本篇文章,我们将会慢慢熟悉语法API,通过语法API来查看解析器,语法树,用于推理和构造它们的实用程序。
理解语法树
Trivia,Token和Node形成了一个完全代表Visual Basic或C#代码片段中所有内容的树
SyntaxTree
它的实例表示整个解析树。SyntaxTree是一个抽象类,具有特定于语言的派生类。要解析特定语言的语法,您需要使用CSharpSyntaxTree(或VisualBasicSyntaxTree)类上的解析方法。
SyntaxNode
它的实例表示的语法结构如声明,语句,子句和表达式。
SyntaxToken
它代表一个单独的关键字,识别符,操作员或标点符号
SyntaxTrivia
它表示语法上无关紧要的信息,例如令牌之间的空白,预处理指令和注释。
下图示例:SyntaxNode: 蓝色 | SyntaxToken: 绿色 | SyntaxTrivia: 红色
遍历语法树
- 新建项目“CodeAnalysisDemo”
- 引入Nuget
Microsoft.CodeAnalysis.CSharp
Microsoft.CodeAnalysis.CSharp.Workspaces
- 命名空间:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
- 准备要分析的代码
using System;
namespace UsingCollectorCS
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World");
}
}
class Student
{
public string Name { get; set; }
}
}
- 核心代码
/// <summary>
///解析语法树
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public SyntaxNode GetRoot(string code)
{
var tree = CSharpSyntaxTree.ParseText(code);
//SyntaxTree的根root
var root = (CompilationUnitSyntax)tree.GetRoot();
//member
var firstmember = root.Members[0];
//命名空间Namespace
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstmember;
//类 class
var programDeclaration = (ClassDeclarationSyntax)helloWorldDeclaration.Members[0];
//方法 Method
var mainDeclaration = (MethodDeclarationSyntax)programDeclaration.Members[0];
//参数 Parameter
var argsParameter = mainDeclaration.ParameterList.Parameters[0];
//查询方法,查询方法名称为Main的第一个参数。
var firstParameters = from methodDeclaration in root.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
where methodDeclaration.Identifier.ValueText == "Main"
select methodDeclaration.ParameterList.Parameters.First();
var argsParameter2 = firstParameters.Single();
return root;
}
- 入口Main方法
var code = @"using System;
namespace UsingCollectorCS
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello World"");
}
}
class Student
{
public string Name { get; set; }
}
}";
var tree = new AnalysisDemo().GetRoot(code);
- Debug调试
经过对比可知以下部分
利用CSharpSyntaxTree.ParseText(code)获取语法树SyntaxTree
利用(CompilationUnitSyntax)tree.GetRoot()获取语法树的跟节点
利用 (NamespaceDeclarationSyntax)root.Members[0]可获取命名空间
利用 (ClassDeclarationSyntax)helloWorldDeclaration.Members[0]可获取类
利用 (MethodDeclarationSyntax)programDeclaration.Members[0]可获取方法
利用linq查询,可从**root.DescendantNodes()**节点内查询方法/参数等成员。
SyntaxWalkers
通常,您需要在语法树中查找特定类型的所有节点,例如,文件中的每个属性声明。
通过扩展CSharpSyntaxWalker类并重写VisitPropertyDeclaration方法,您可以在不事先知道其结构的情况下处理语法树中的每个属性声明。
CSharpSyntaxWalker是一种特殊的SyntaxVisitor,它以递归方式访问节点及其每个子节点。
我们先来演示CSharpSyntaxWalker的两个虚virtual方法VisitUsingDirective 和VisitPropertyDeclaration
- 核心代码如下:
/// <summary>
/// 收集器
/// </summary>
public class UsingCollector : CSharpSyntaxWalker
{
public readonly Dictionary<string, List<string>> models = new Dictionary<string, List<string>>();
public readonly List<UsingDirectiveSyntax> Usings = new List<UsingDirectiveSyntax>();
public override void VisitUsingDirective(UsingDirectiveSyntax node) {
if (node.Name.ToString() != "System" &&
!node.Name.ToString().StartsWith("System."))
{
this.Usings.Add(node);
}
}
public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
{
var classnode = node.Parent as ClassDeclarationSyntax;
if (!models.ContainsKey(classnode.Identifier.ValueText))
{
models.Add(classnode.Identifier.ValueText, new List<string>());
}
models[classnode.Identifier.ValueText].Add(node.Identifier.ValueText);
}
}
/// <summary>
/// 演示CSharpSyntaxWalker
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public UsingCollector GetCollector(string code)
{
var tree = CSharpSyntaxTree.ParseText(code);
var root = (CompilationUnitSyntax)tree.GetRoot();
var collector = new UsingCollector();
collector.Visit(root);
return collector;
}
- Main调用入口:
var code2 =
@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace TopLevel
{
using Microsoft;
using System.ComponentModel;
namespace Child1
{
using Microsoft.Win32;
using System.Runtime.InteropServices;
class Foo {
public string FChildA{get;set;}
public string FChildB{get;set;}
}
}
namespace Child2
{
using System.CodeDom;
using Microsoft.CSharp;
class Bar {
public string BChildA{get;set;}
public string BChildB{get;set;}
}
}
}";
var collector = new AnalysisDemo().GetCollector(code2);
foreach (var directive in collector.Usings)
{
Console.WriteLine($"Name:{directive.Name}");
}
Console.WriteLine($"models:{JsonConvert.SerializeObject(collector.models)}");
- 执行结果
我们可以得出结论
VisitUsingDirective 主要用于获取Using命名空间
VisitPropertyDeclaration主要用于获取属性。
总结
本篇文章主要讲了
语法树SyntaxTree,以及SyntaxNode,SyntaxToken,SyntaxTrivia。
通过重写CSharpSyntaxWalker的虚方法,可以实现自定义获取。
附上官方截取的部分流程图
Roslyn编译管道功能区
API图层
Roslyn由两个主要的API层组成 - 编译器API和工作区API。
源码
参考链接
Getting Started C# Syntax Analysis
从零开始学习 dotnet 编译过程和 Roslyn 源码分析
Roslyn入门(一)-C#语法分析的更多相关文章
- Roslyn 入门:使用 .NET Core 版本的 Roslyn 编译并执行跨平台的静态的源码
Roslyn 是微软为 C# 设计的一套分析器,它具有很强的扩展性.以至于我们只需要编写很少量的代码便能够编译并执行我们的代码. 作为 Roslyn 入门篇文章之一,你将可以通过本文学习如何开始编写一 ...
- Roslyn 入门:使用 Visual Studio 的语法可视化窗格查看和了解代码的语法树
使用 Visual Studio 提供的 Syntax Visualizer,我们可以实时看到一个代码文件中的语法树.这对我们基于 Roslyn 编写静态分析和修改工具非常有帮助.本文将介绍如何安装它 ...
- Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码
Roslyn 是微软为 C# 设计的一套分析器,它具有很强的扩展性.以至于我们只需要编写很少量的代码便能够分析我们的项目文件. 作为 Roslyn 入门篇文章,你将可以通过本文学习如何开始编写一个 R ...
- Roslyn入门(二)-C#语义
先决条件 Visual Studio 2017 .NET Compiler Platform SDK Rosyln入门(一)-C#语法分析 简介 今天,Visual Basic和C#编译器是黑盒子:输 ...
- Elasticsearch入门和查询语法分析(ik中文分词)
全文搜索现在已经是很常见的功能了,当然你也可以用mysql加Sphinx实现.但开源的Elasticsearch(简称ES)目前是全文搜索引擎的首选.目前像GitHub.维基百科都使用的是ES,它可以 ...
- Roslyn 学习笔记(二)
参考:https://github.com/dotnet/roslyn/wiki/Getting-Started-C%23-Syntax-Analysis 语法分析过程主要用到以下类或结构: Synt ...
- 在 Roslyn 分析语法树时添加条件编译符号的支持
我们在代码中会写 #if DEBUG 或者 [Conditional("DEBUG")] 来使用已经定义好的条件编译符号.而定义条件编译符号可以在代码中使用 #define WAL ...
- Roslyn 如何使用 MSBuild Copy 复制文件
本文告诉大家如何在 MSBuild 里使用 Copy 复制文件 需要知道 Rosyln 是 MSBuild 的 dotnet core 版本. 在 MSBuild 里可以使用很多命令,本文告诉大家如何 ...
- Roslyn 使用 Directory.Build.props 管理多个项目配置
在一些大项目需要很多独立的仓库来做,每个仓库之间都会有很多相同的配置,本文告诉大家如何通过 Directory.Build.props 管理多个项目配置 在我的 MVVM 框架需要三个不同的库,一个是 ...
随机推荐
- BigDecimal遇到的问题,大伙也说说
一:相除精度丢失的问题 BigDecimal的api除法相对加减乘要实现的复杂多了,只介绍常用的我遇到的问题: 问题:两数相除,如果9/3=3整除没问题,但是10/3=0.33333333...... ...
- SQL Server 2012 读写分离设置 - AlsoIn
原文转至:http://www.tuicool.com/articles/a6rmiam/ 引用: http://technet.microsoft.com/zh-cn/library/jj16176 ...
- Microsoft .NET Core 1.0.0 VS 2015 Tooling Preview 2 Uninstall Failed
卸载过程中总是卸载失败报0x80070001:函数不明确错误.转遍了各大论坛和QQ,最终还是在stackoverflow上找到了答案... 原因是我卸载时选择的DotNetCore.1.0.0-VS2 ...
- Linux常用命令大全(新手入门)
系统信息: arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SM ...
- Debian-Linux配置网卡网络方法
Debian不同于centos系统,网卡配置不是在/etc/sysconfig/network-scrip里面,而是在/etc/network/interfaces里面 1.Debian网络配置 配置 ...
- 函数重载(overload)
重载的定义及特点 在同一个类中,允许存在一个以上的同名函数, 只要他们的参数个数或者参数类型不同(不仅指两个重载方法的参数类型不同,还指相同参数拥有不同的参数类型顺序)就构成重载. 重载只和参数列表有 ...
- 从研发到市场,一个C#程序员半年神奇之旅
序 距离上次在博客园发布文章已经过了大约有一年了,由于最近一系列神奇的际遇,让我非常强烈意愿的提起笔来给大家描述我最近一段时间的经历,希望大家根据我的经历做一些参考,我尽量写的逻辑通顺,如果各位兄弟阅 ...
- vue_02 开发过程中的问题记载
1.package.json 运行 npm start 执行的是npm run dev 实际上执行的是“dev” : node build/dev-server.js这一条 跑的是build目录下d ...
- 通过SQL直接插入、修改ArcGIS SDE空间表中的数据
基于Arcgis Server 10.1 +Oracle 11g环境测试 ArcGIS SDE ? 1 2 INSERT INTO CAMERA_INFO(OBJECTID,ID,SHAPE) ...
- RESTful API实战笔记(接口设计及Java后端实现)
写在前面的话 原计划这部分代码的更新也是上传到ssm-demo仓库中,因为如下原因并没有这么做: 有些使用了该项目的朋友建议重新创建一个仓库,因为原来仓库中的项目太多,结构多少有些乱糟糟的. 而且这次 ...