演示环境

Visual Studio 2017

.NET Compiler Platform SDK

简介

今天,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。

源码

CsharpFanDemo

参考链接

Roslyn-Overview

Getting Started C# Syntax Analysis

从零开始学习 dotnet 编译过程和 Roslyn 源码分析

手把手教你写 Roslyn 修改编译

Roslyn入门(一)-C#语法分析的更多相关文章

  1. Roslyn 入门:使用 .NET Core 版本的 Roslyn 编译并执行跨平台的静态的源码

    Roslyn 是微软为 C# 设计的一套分析器,它具有很强的扩展性.以至于我们只需要编写很少量的代码便能够编译并执行我们的代码. 作为 Roslyn 入门篇文章之一,你将可以通过本文学习如何开始编写一 ...

  2. Roslyn 入门:使用 Visual Studio 的语法可视化窗格查看和了解代码的语法树

    使用 Visual Studio 提供的 Syntax Visualizer,我们可以实时看到一个代码文件中的语法树.这对我们基于 Roslyn 编写静态分析和修改工具非常有帮助.本文将介绍如何安装它 ...

  3. Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码

    Roslyn 是微软为 C# 设计的一套分析器,它具有很强的扩展性.以至于我们只需要编写很少量的代码便能够分析我们的项目文件. 作为 Roslyn 入门篇文章,你将可以通过本文学习如何开始编写一个 R ...

  4. Roslyn入门(二)-C#语义

    先决条件 Visual Studio 2017 .NET Compiler Platform SDK Rosyln入门(一)-C#语法分析 简介 今天,Visual Basic和C#编译器是黑盒子:输 ...

  5. Elasticsearch入门和查询语法分析(ik中文分词)

    全文搜索现在已经是很常见的功能了,当然你也可以用mysql加Sphinx实现.但开源的Elasticsearch(简称ES)目前是全文搜索引擎的首选.目前像GitHub.维基百科都使用的是ES,它可以 ...

  6. Roslyn 学习笔记(二)

    参考:https://github.com/dotnet/roslyn/wiki/Getting-Started-C%23-Syntax-Analysis 语法分析过程主要用到以下类或结构: Synt ...

  7. 在 Roslyn 分析语法树时添加条件编译符号的支持

    我们在代码中会写 #if DEBUG 或者 [Conditional("DEBUG")] 来使用已经定义好的条件编译符号.而定义条件编译符号可以在代码中使用 #define WAL ...

  8. Roslyn 如何使用 MSBuild Copy 复制文件

    本文告诉大家如何在 MSBuild 里使用 Copy 复制文件 需要知道 Rosyln 是 MSBuild 的 dotnet core 版本. 在 MSBuild 里可以使用很多命令,本文告诉大家如何 ...

  9. Roslyn 使用 Directory.Build.props 管理多个项目配置

    在一些大项目需要很多独立的仓库来做,每个仓库之间都会有很多相同的配置,本文告诉大家如何通过 Directory.Build.props 管理多个项目配置 在我的 MVVM 框架需要三个不同的库,一个是 ...

随机推荐

  1. Hacker News API

    Hacker News API中的URI和版本 API都由https://hacker-news.firebaseio.com提供. 单个条目信息 故事,评论,招聘,问答,以及投票都叫做条目.它们有各 ...

  2. 【PAT】B1001 害死人不偿命的(3n+1)猜想

    超级简单题 偶数砍掉一半, 奇数乘三加一砍掉一半 #include<stdio.h> int mian(){ int n,step=0; scanf("%d",n); ...

  3. MyBatis实现模糊查询的几种方式

    在学习MyBatis过程中想实现模糊查询,可惜失败了.后来上百度上查了一下,算是解决了.记录一下MyBatis实现模糊查询的几种方式. 数据库表名为test_student,初始化了几条记录,如图: ...

  4. activiti获取可回退的节点

    在处理流程回退时,需要获取某个节点当前可以回退到的节点,简单分析下: 1. 只支持回退到userTask. 2. 如果流程流转过某节点时生成了多个任务,从其中某一个任务回退到该节点后,不处理另外的任务 ...

  5. Django之ORM查询复习与cookie

    ORM查询总结: models.Book.objects.filter(**kwargs): querySet [obj1,obj2] models.Book.objects.filter(**kwa ...

  6. UVA506-System Dependencies(拓扑序)

    Problem UVA506-System Dependencies Accept:285  Submit:2824 Time Limit: 3000 mSec Problem Description ...

  7. luogu P2000 拯救世界

    嘟嘟嘟 题目有点坑,要你求的多少大阵指的是召唤kkk的大阵数 * lzn的大阵数,不是相加. 看到这个限制条件,显然要用生成函数推一推. 比如第一个条件"金神石的块数必须是6的倍数" ...

  8. 转 一个web项目web.xml的配置中<context-param>配置作用

    一个web项目web.xml的配置中<context-param>配置作用   <context-param>的作用:web.xml的配置中<context-param& ...

  9. keystore密钥文件使用的算法-PBKDF2WithHmacSHA1 和Scrypt

    PBKDF2 简单而言就是将salted hash进行多次重复计算,这个次数是可选择的.如果计算一次所需要的时间是1微秒,那么计算1百万次就需要1秒钟.假如攻击一个密码所需的rainbow table ...

  10. Mybatis集成(转)

    文章转自http://blog.csdn.net/l454822901/article/details/51829653 什么是Mybatis MyBatis 本是apache的一个开源项目iBati ...