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 框架需要三个不同的库,一个是 ...
随机推荐
- python第九天----今天来晚了!
作业 1. HAproxy配置文件操作1. 根据用户输入输出对应的backend下的server信息2. 可添加backend 和sever信息3. 可修改backend 和sever信息4. 可删除 ...
- Asp.net Mvc、webApi配置允许跨域
Web.config 下<system.webServer> 节点下配置 <httpProtocol> <customHeaders> <add name=& ...
- php二维数组去重
php二维数组去重 前言:php一维数组去重很简单,直接array_unique($arr)即可,但是二维数组去重就得自己去写了 二维数组去重方法: /* * 二维数组去重 * 注意:二维数组中的元素 ...
- Django学习---快速搭建搜索引擎(haystack + whoosh + jieba)
Django下的搜索引擎(haystack + whoosh + jieba) 软件安装 haystack是django的开源搜索框架,该框架支持Solr,Elasticsearch,Whoosh, ...
- PHP Excel导入数据到MySQL数据库
数据导出已经有了,怎么能没有数据导入呢,同样使用TP5框架,首先需要下载phpexcel.zip,放到第三方类库目录vendor目录下,然后有一个页面可以让你选择要导入的Excel文件,然后点击导入按 ...
- call()和apply()
call()和apply()方法类似,区别是,call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组. 当一个函数在其主体中使用 this 关键字时,可以通过使用 ...
- [Oracle] ROWNUM和分页
rownum是oracle的一个伪劣,它的顺序依据从表中获取记录的顺序递增,这里要注意的是:由于记录在表中是无序存放的.因此你无法通过简单的rownum和order by的组合获得相似TOP N的结果 ...
- day12 Python列表
list#类 列表概括 li = [1,2,13,["石振文",["19", 10],"庞麦郎"],"charon",& ...
- Java连接Redis之redis的增删改查
一.新建一个maven工程,工程可以以jar的形式或war都行,然后导入正确的依赖 <project xmlns="http://maven.apache.org/POM/4.0.0& ...
- QT获取窗口大小和位置等信息
QT窗口尺寸,窗口大小和大小改变引起的事件 QResizeEvent. 来源:http://blog.csdn.net/dbzhang800/article/details/6741344?reloa ...