Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码
Roslyn 是微软为 C# 设计的一套分析器,它具有很强的扩展性。以至于我们只需要编写很少量的代码便能够分析我们的项目文件。
作为 Roslyn 入门篇文章,你将可以通过本文学习如何开始编写一个 Roslyn 扩展项目,如何开始分析一个解决方案(.sln)中项目(.csproj)的代码文件(.cs)。
本文是 Roslyn 入门系列之一:
- Roslyn 入门:使用 Visual Studio 的语法可视化(Syntax Visualizer)窗格查看和了解代码的语法树
- Roslyn 入门:使用 .NET Core 版本的 Roslyn 编译并执行跨平台的静态的源码
- Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码(本文)
如果你希望真实地静态分析一个实际项目,并且理解这样的分析过程是如何进行的(而不只是写个 demo),那么本文的所有内容都将是必要的。
准备工作
为了能够进行后面关键的操作,我们需要先有一个能跑起来的项目。
▲ 在 Visual Studio 新建项目,选择“控制台程序(.NET Framework)”
在目前({% include date.html date=page.date %}),如果我们需要像本文一样分析现有的解决方案和项目,那么 .NET Framework 是必须的;如果只是分析单个文件,那么也可以选择 .NET Core,参见 Roslyn 入门:使用 .NET Core 版本的 Roslyn 编译并执行跨平台的静态的源码。
当然,如果你有一个现成的 .NET Core 项目,可以通过修改 .csproj 文件改成 .NET Framework 的:
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><!-- 从 netcoreapp2.0 改成 net471,因为 NuGet 包中的 ValueTuple 与 net47 不兼容,所以只能选择 net471 或以上 --><TargetFramework>net471</TargetFramework></PropertyGroup>
</Project>
现在,我们有了一个可以开始写代码的 Program.cs 文件,接下来就可以正式开始入门了。
安装必要的 NuGet 包
在 NuGet 包管理器中搜索并安装 Microsoft.CodeAnalysis 包 —— 这是一个包含 Roslyn 所有 API 的各种 NuGet 包的合集。
当然,如果你只是做一些特定的事情,当然不需要安装这么全的 NuGet 包,像 Roslyn 静态分析 - 林德熙 的 demo 和 Roslyn 编译与执行 - 吕毅 中的教程就不需要安装所有 NuGet 包。
特别注意!!!如果前面你是通过 .NET Core 项目改过来的,那么还需要额外安装以下三个 NuGet 包,否则运行时会无法打开解决方案和项目。
Microsoft.Build
Microsoft.Build.Tasks.Core
System.Threading.Tasks.Dataflow
打开一个解决方案/项目和其中的文件
现在,我们使用这些代码打开解决方案。我以 MSTestEnhancer 为例:
// 打开 MSTestEnhancer(https://github.com/dotnet-campus/MSTestEnhancer/) 解决方案文件。
// 注意这里的 MSBuildWorkspace.Create() 会返回 WorkSpace 的实例。
// 虽然 WorkSpace 是跨平台的,但是 MSBuildWorkspace 仅在 Windows 下可用。
var solution = await MSBuildWorkspace.Create().OpenSolutionAsync(@"D:\Developments\Open\MSTestEnhancer\MSTest.Extensions.sln");// 从解决方案中选出 MSTest.Extensions 项目。
var project = solution.Projects.First(x => x.Name == "MSTest.Extensions");// 从 MSTest.Extensions 项目中选出我们要分析的 ContractTestContext.cs 文件。
// 这里只是一个示例,所以我们只分析一个文件。你可以从 Documents 集合中找出这个项目的所有文件进行分析。
var document = project.Documents.First(x =>x.Name.Equals("ContractTestContext.cs", StringComparison.InvariantCultureIgnoreCase));
分析代码
我们要分析的代码大致是这样的:
// 这里是 using,省略。
// 这里是命名空间,省略。
public class ContractTestContext<T>
{// 这是代码的细节,省略。
}
现在,我们开始使用 Roslyn API 找出里面的泛型 T
。
这里,我们必须引入一个概念 —— Syntax Rewriter。
语法重写——Syntax Rewriter
Roslyn 对 C# 代码进行分析的一个非常关键的 API 是 CSharpSyntaxRewriter
——这是一个专门用来给你继承的类。CSharpSyntaxRewriter
是访问者模式中访问者的一个实现,如果你不了解访问者模式,推荐阅读 23种设计模式(9):访问者模式 - CSDN博客 进行了解,否则我们后面的代码你将只能跟着我写,而不能明白其中的含义。
当你阅读到这里时,我开始假设你已经了解了访问者模式了。
我们每个人都可能会写出不同的基于 Roslyn 的分析器,这些分析器通常都会对不同文件的 C# 语法树进行不同的操作;于是,我们通过重写 CSharpSyntaxRewriter
可以实现各种各样不同的操作。在访问者模式中,由于 C# 的语法在一个 C# 版本发布之后就会确定,其中各种各样类型的语法对应访问者模式中的各种不同类型的数据,Roslyn 为我们构建的语法树对应访问者模式中需要访问的庞大的数据结构。由于 Roslyn 的语法树是非常庞大的,以至于对其进行遍历也是一个非常复杂的操作;所以 Roslyn 通过访问者模式为我们封装了这种复杂的遍历过程,我们只需要重写 CSharpSyntaxRewriter
就可以实现对某种特定语法节点的操作。
现在,我们编写一个用于找出泛型参数 T
的 Syntax Rewriter。
class TypeParameterVisitor : CSharpSyntaxRewriter
{public override SyntaxNode VisitTypeParameterList(TypeParameterListSyntax node){var lessThanToken = this.VisitToken(node.LessThanToken);var parameters = this.VisitList(node.Parameters);var greaterThanToken = this.VisitToken(node.GreaterThanToken);return node.Update(lessThanToken, parameters, greaterThanToken);}
}
其实这段代码就是 CSharpSyntaxRewriter
基类中的代码,我把它贴出来可以帮助我们理解它。你也依然需要将他放入到我们的项目中,因为我们接下来的代码就开始要使用它了。
如果你想了解更多语法节点,推荐另一篇入门文章:Roslyn 入门:使用 Visual Studio 的语法可视化(Syntax Visualizer)窗格查看和了解代码的语法树。
访问泛型参数
现在,我们继续在之前打开解决方案和项目文件的代码后面增添代码:
// 从我们一开始打开的项目文件中获取语法树。
var tree = await document.GetSyntaxTreeAsync();
var syntax = tree.GetCompilationUnitRoot();// 使用我们刚刚重写 CSharpSyntaxRewriter 的类来访问语法树。
var visitor = new TypeParameterVisitor();
var node = visitor.Visit(syntax);// 得到的 node 是新的语法树节点,
// 如果我们在 `TypeParameterVisitor` 中修改了语法树,
// 那么这里就会得到修改后的 node 节点。
// 我们可以通过这个 node 节点做各种后续的操作。
如果我们使用 node 的方式是修改代码,那么可以使用 var text = node.GetText();
来得到新的语法树生成的代码,使用这段文本替换之前的文本可以达到修改代码的目的。不过,这不是本文的重点,本文的重点依然在入门。
现在,整合以上的三大段代码,你的项目应该能够完整地跑起来了。哪三段?1. 打开项目文件;2. TypeParameterVisitor
;3. 访问泛型参数。其中 1 和 3 写在一个方法中,2 是一个新类。
分析这个泛型参数
直到现在,我们所写的任何代码都还只是为了使使用 Roslyn API 的代码能够跑起来,没有进行任何实质上的分析。接下来,我们会修改 CSharpSyntaxRewriter
以进行真正的分析。不过在此之前,我假设上面的代码你是能正常跑起来而且没有错误的。(如果不行,就在下面留言吧!留言有邮件通知的,我会在第一时间回复你。)
如果你不了解 Roslyn,强烈建议去 VisitTypeParameterList
重写方法中打一个断点观察 lessThanToken
parameters
greaterThanToken
这几个实例的含义。lessThanToken
就是 <
,greaterThanToken
就是 >
;而 parameters
是一个泛型参数列表,在这里,是一个 T
。
现在,我们构造一个自己的泛型参数列表试试,名字不是 T
了,而是 TParameter
。
var parameters = new SeparatedSyntaxList<TypeParameterSyntax>();
parameters = parameters.Add(SyntaxFactory.TypeParameter("TParameter"));
特别注意:SeparatedSyntaxList
的 Add
操作不会修改原集合,而是会返回一个新的集合!所以上面 Add
之后的赋值语句不能少!这样的设计应该是为了避免遍历语法树的时候语法树被修改导致遍历不可控。
于是,我们的 TypeParameterVisitor
变成了这样:
class TypeParameterVisitor : CSharpSyntaxRewriter
{public override SyntaxNode VisitTypeParameterList(TypeParameterListSyntax node){// 构造一个自己的泛型列表,名字改为了 TParameter。var parameters = new SeparatedSyntaxList<TypeParameterSyntax>();parameters = parameters.Add(SyntaxFactory.TypeParameter("TParameter"));// 依然保留之前的更新语法节点的方法。// 这样,我们将会在语法树访问结束后得到新的语法树。var lessThanToken = this.VisitToken(node.LessThanToken);var greaterThanToken = this.VisitToken(node.GreaterThanToken);return node.Update(lessThanToken, parameters, greaterThanToken);}
}
总结
我们总共编写了两个关键类:
- Program
- Main(用于打开项目和文件,并调用 TypeParameterVisitor 遍历语法树)
需要注意,Main 函数只有 C#7.2 及以上才支持async
,如果没有这么高,需要再编写一个新函数,然后在 Main 里面调用它。
- Main(用于打开项目和文件,并调用 TypeParameterVisitor 遍历语法树)
- TypeParameterVisitor
- VisitTypeParameterList(用于遍历和修改语法树中的泛型参数列表)
以上便是分析和修改 Roslyn 语法树的简单实例了,我将整个 Program.cs 文件贴在下面,以便整体查看。
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;namespace Walterlv.Demo.Roslyn
{class Program{static void Main(string[] args){RunAsync().Wait();}private static async Task RunAsync(){var solution = await MSBuildWorkspace.Create().OpenSolutionAsync(@"D:\Developments\Open\MSTestEnhancer\MSTest.Extensions.sln");var project = solution.Projects.First(x => x.Name == "MSTest.Extensions");var document = project.Documents.First(x =>x.Name.Equals("ContractTestContext.cs", StringComparison.InvariantCultureIgnoreCase));var tree = await document.GetSyntaxTreeAsync();var syntax = tree.GetCompilationUnitRoot();var visitor = new TypeParameterVisitor();var node = visitor.Visit(syntax);var text = node.GetText();File.WriteAllText(document.FilePath, text.ToString());}}class TypeParameterVisitor : CSharpSyntaxRewriter{public override SyntaxNode VisitTypeParameterList(TypeParameterListSyntax node){var syntaxList = new SeparatedSyntaxList<TypeParameterSyntax>();syntaxList = syntaxList.Add(SyntaxFactory.TypeParameter("TParameter"));var lessThanToken = this.VisitToken(node.LessThanToken);var greaterThanToken = this.VisitToken(node.GreaterThanToken);return node.Update(lessThanToken, syntaxList, greaterThanToken);}}
}
参考资料
- 23种设计模式(9):访问者模式 - CSDN博客
- John Koerner - Using a CSharp Syntax Rewriter
- Learn Roslyn Now: Part 5 CSharpSyntaxRewriter – Shotgun Debugging
- Code Generation with Roslyn: a Skeleton Class from UML - Federico Tomassetti - Software Architect
转载于:https://www.cnblogs.com/walterlv/p/10236480.html
Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码相关推荐
- 圣诞节PNG免扣素材|轻松向现有项目中添加节日元素!
制作任何东西,从定制的圣诞卡,图标,可以用于网站设计或包装,甚至图形,照片和插图,PNG免扣格式图片素材,无疑可以帮助你有效的完成项目. 节日的装饰不仅仅包括挂一棵圣诞树和添加金属箔.装饰也可以延伸到 ...
- webpack入门学习笔记10 —— 在项目中使用图片资源
1. 写在前面 在前端项目中,图片是必不可少的一种资源.在使用图片的时候,我们可以有以下几种方式: 在 .html 文件中,通过 <img src="" alt=" ...
- 如何使用mysql建立项目_【dbForge Studio for MySQL入门教程】如何在项目中使用数据库对象和如何使用项目构建配置...
重命名数据库对象 要从项目重命名对象,请执行以下步骤: 1. 在" Schema View "窗口中右键单击该对象. 2. 从快捷菜单中选择" Rename " ...
- 【杂谈】有三AI开源项目中的代码和教程,来学习的粉丝们应该要知道了
我们的开源项目https://github.com/longpeng2008/yousan.ai早就存在了,但还是经常会遇到不知道这个项目的学习者.这个项目是我们目前唯一的技术性开源项目,里面包含适合 ...
- 计算项目中的代码行数:Count the Lines of Code (LOC)
很多时候打开一个大的项目工程时,我们会想知道这个项目有多少行代码. Visual Studio 自带这个功能,在分析->窗口->代码度量值结果,但是这个功能比较耗时,因为同时分析了代码的耦 ...
- java滥用接口_吐槽一下项目中的代码坏味道:滥用java常量
我们的项目中是否充斥着类似以下的代码呢?定义一个专门存放常量的java类(接口),非常多其它类依赖该常量类. public interface IConstant { int ZERO = 0; St ...
- 统计一下项目中的代码有多少行是你贡献的
文章目录 方法一 Ubuntu Mac 方法二 Ubuntu Mac 同一作者不同名称的问题 输出统计耗时 Ubuntu 最终脚本 Mac 最终脚本 本篇内容仅对 Linux/Mac 友好,Windo ...
- eclipse更新项目中的代码,必须手动clean才能清除
调试rcp项目时候,遇到一个问题:大致流程是这样 ----->改变了项目中的某个代码,结果在项目名字上出现红色的叉,上网查找解决方案,有的说,勾选Build Automatically(一直勾选 ...
- 在现有项目中使用AspNet Identity 2.0 实战
感谢@LAgess 的解答:http://q.cnblogs.com/q/68698/ 办法一: 1.用 NuGet 安装 Identity. 2.添加链接字符串: <add name=&quo ...
- Immutable入门使用_在react项目中使用redux-immutable
定义 不可变数据 (Immutable Data )就是一旦创建,就不能再被更改的数据.对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象.Immutabl ...
最新文章
- CF20C Dijkstra?( Dijkstra!练手)难度⭐⭐⭐
- HYSBZ - 1101——莫比乌斯反演
- gb50243-2016通风与空调工程施工质量验收规范_07K304 空调机房设计与安装
- 30秒内限制函数只被调用一次
- 采用随机的'User-Agent'提高博客的访问量
- 机器学习和ML.NET简介-第1部分
- 在网页中引用DWG控件,交互绘图,和响应鼠标点击对象的方法
- 分享个Duilib中基于wke的浏览器控件
- php7.3手册_php7.3.8手册下载
- eviews建立时间序列模型_模型建立——时间序列 eviews协整检验(EG两步法(Engle-Granger))...
- python同步油管用户信息
- 机器人手眼标定Ax=xB(eye to hand和eye in hand)及平面九点法标定
- LightOJ1336
- HDU6441 Find Integer(2018CCPC网络赛,费马大定理)
- SpringBoot整合Redis实现排行榜功能
- 2023北京老博会·老年用品展·老年食品展·北京老年助浴展
- 微信小程序 实时搜索并高亮关键字
- 苹果手机测距离_手机上有个小功能,可以精确的测量距离,比卷尺还要好用
- 数据分析案例:APP热点标签分析
- 【mmdetection小目标检测教程】三、使用sahi库切分高分辨率图片,一键生成coco格式数据集
热门文章
- mapabc高德地图区域收缩事件监听
- erdas2015几何校正模块在哪_什么是几何校正和正射校正还有怎样用erdas做几何校正...
- 给想去北大青鸟或是其他机构培训的同学
- 数据结构—快速排序 C语言代码实现
- 天正电气2014的安装
- 基于Neo4j计算网络节点中心性(Closeness、betweenness Centrality)
- 如何获得Win10联想锁屏的壁纸(联想锁屏的壁纸灰常好看,那么我们如何获得呢?)
- 黑苹果 10.14.6 安装记录
- MySQL读写分离的三种实现方案
- [存储] Cobar使用文档(可用作MySQL大型集群解决方案)