Roslyn 是微软为 C# 设计的一套分析器,它具有很强的扩展性。以至于我们只需要编写很少量的代码便能够编译并执行我们的代码。

作为 Roslyn 入门篇文章之一,你将可以通过本文学习如何开始编写一个 Roslyn 扩展项目 —— 编译一个类,然后执行其中的一段代码。


本文是 Roslyn 入门系列之一:

 

我们希望做什么?

是否有过在编译期间修改一段代码的想法呢?

我曾经在 生成代码,从 T 到 T1, T2, Tn —— 自动生成多个类型的泛型 一文中提到过这样的想法,在这篇文章中,我希望只编写泛型的一个参数的版本 Demo<T>,然后自动生成 2~16 个参数的版本 Demo<T1, T2>, Demo<T1, T2, T3>Demo<T1, T2, ... T16>。不过,在那篇文章中,我写了一个应用程序来完成这样的事情。我在另一篇文章 如何创建一个基于命令行工具的跨平台的 NuGet 工具包 中说到我们可以将这样的应用程序打包成一个 NuGet 工具包。也就是说,利用这两种不同的技术,我们可以制作一个在编译期间生成多个泛型的 NuGet 工具包。

不过,这样的生成方式不够通用。今天我们想生成泛型,明天我们想生成多语言类,后天我们又想生成代理类。能否做一种通用的方式来完成这样的任务呢?

于是,我想到可以使用 Roslyn。在项目中编写一段转换代码,我们使用通用的方式去编译和执行这段代码,以便完成各种各样日益增加的类型转换需求。具体来说,就是 使用 Roslyn 编译一段代码,然后执行它

准备工作

与之前在 Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码 中的不同,我们这次无需打开解决方案或者项目,而是直接寻找并编译源代码文件。所以(利好消息),我们这回可以使用 .NET Core 跨平台版本的 Roslyn 了。所以为了充分有跨平台特性,我们创建控制台应用 (.NET Core)


▲ 千万不要吐槽相比于上一个入门教程来说,这次的界面变成了英文

安装必要的 NuGet 包

这次不需要完整的 .NET Framework 环境,也不需要打开解决方案和项目这种重型 API,所以一个简单的 NuGet 包足矣:

准备一份用于编译和执行代码文件

我直接使用 生成代码,从 T 到 T1, T2, Tn —— 自动生成多个类型的泛型 这篇文章中的例子。把其中最关键的文件拿来用于编译和生成试验。

  1. using System.Linq;
  2. using static System.Environment;
  3. namespace Walterlv.Demo.Roslyn
  4. {
  5. public class GenericGenerator
  6. {
  7. private static readonly string GeneratedAttribute =
  8. @"[System.CodeDom.Compiler.GeneratedCode(""walterlv"", ""1.0"")]";
  9. public string Transform(string originalCode, int genericCount)
  10. {
  11. if (genericCount == 1)
  12. {
  13. return originalCode;
  14. }
  15. var content = originalCode
  16. // 替换泛型。
  17. .Replace("<out T>", FromTemplate("<{0}>", "out T{n}", ", ", genericCount))
  18. .Replace("Task<T>", FromTemplate("Task<({0})>", "T{n}", ", ", genericCount))
  19. .Replace("Func<T, Task>", FromTemplate("Func<{0}, Task>", "T{n}", ", ", genericCount))
  20. .Replace(" T, Task>", FromTemplate(" {0}, Task>", "T{n}", ", ", genericCount))
  21. .Replace("(T, bool", FromTemplate("({0}, bool", "T{n}", ", ", genericCount))
  22. .Replace("var (t, ", FromTemplate("var ({0}, ", "t{n}", ", ", genericCount))
  23. .Replace(", t)", FromTemplate(", {0})", "t{n}", ", ", genericCount))
  24. .Replace("return (t, ", FromTemplate("return ({0}, ", "t{n}", ", ", genericCount))
  25. .Replace("<T>", FromTemplate("<{0}>", "T{n}", ", ", genericCount))
  26. .Replace("(T value)", FromTemplate("(({0}) value)", "T{n}", ", ", genericCount))
  27. .Replace("(T t)", FromTemplate("({0})", "T{n} t{n}", ", ", genericCount))
  28. .Replace("(t)", FromTemplate("({0})", "t{n}", ", ", genericCount))
  29. .Replace("var t =", FromTemplate("var ({0}) =", "t{n}", ", ", genericCount))
  30. .Replace(" T ", FromTemplate(" ({0}) ", "T{n}", ", ", genericCount))
  31. .Replace(" t;", FromTemplate(" ({0});", "t{n}", ", ", genericCount))
  32. // 生成 [GeneratedCode]。
  33. .Replace(" public interface ", $" {GeneratedAttribute}{NewLine} public interface ")
  34. .Replace(" public class ", $" {GeneratedAttribute}{NewLine} public class ")
  35. .Replace(" public sealed class ", $" {GeneratedAttribute}{NewLine} public sealed class ");
  36. return content.Trim();
  37. }
  38. private static string FromTemplate(string template, string part, string seperator, int count)
  39. {
  40. return string.Format(template,
  41. string.Join(separator, Enumerable.Range(1, count).Select(x => part.Replace("{n}", x.ToString()))));
  42. }
  43. }
  44. }

这份代码你甚至可以直接复制到你的项目中,一定是可以编译通过的。

编译这份代码

使用 Roslyn 编译一份代码是非常轻松愉快的。写出以下这三行就够了:

  1. var syntaxTree = CSharpSyntaxTree.ParseText("那份代码的全文内容");
  2. var compilation = CSharpCompilation.Create("assemblyname", new[] { syntaxTree },
  3. options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
  4. var result = compilation.Emit(ms);

好吧,其实我是开玩笑的,这三行代码确实能够跑通过,不过得到的 result 是编译不通过的结局。为了能够在多数情况下编译通过,我写了更多的代码:

  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Reflection;
  5. using Microsoft.CodeAnalysis;
  6. using Microsoft.CodeAnalysis.CSharp;
  7. namespace Walterlv.Demo.Roslyn
  8. {
  9. class Program
  10. {
  11. static void Main(string[] args)
  12. {
  13. // 大家都知道在代码中写死文件路径是不对的,不过,我们这里是试验。放心,我会改的!
  14. var file = @"D:\Development\Demo\Walterlv.Demo.Roslyn\Walterlv.Demo.Roslyn.Tests\GenericGenerator.cs";
  15. var originalText = File.ReadAllText(file);
  16. var syntaxTree = CSharpSyntaxTree.ParseText(originalText);
  17. var type = CompileType("GenericGenerator", syntaxTree);
  18. // 于是我们得到了编译后的类型,但是还不知道怎么办。
  19. }
  20. private static Type CompileType(string originalClassName, SyntaxTree syntaxTree)
  21. {
  22. // 指定编译选项。
  23. var assemblyName = $"{originalClassName}.g";
  24. var compilation = CSharpCompilation.Create(assemblyName, new[] { syntaxTree },
  25. options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
  26. .AddReferences(
  27. // 这算是偷懒了吗?我把 .NET Core 运行时用到的那些引用都加入到引用了。
  28. // 加入引用是必要的,不然连 object 类型都是没有的,肯定编译不通过。
  29. AppDomain.CurrentDomain.GetAssemblies().Select(x => MetadataReference.CreateFromFile(x.Location)));
  30. // 编译到内存流中。
  31. using (var ms = new MemoryStream())
  32. {
  33. var result = compilation.Emit(ms);
  34. if (result.Success)
  35. {
  36. ms.Seek(0, SeekOrigin.Begin);
  37. var assembly = Assembly.Load(ms.ToArray());
  38. return assembly.GetTypes().First(x => x.Name == originalClassName);
  39. }
  40. throw new CompilingException(result.Diagnostics);
  41. }
  42. }
  43. }
  44. }

执行编译后的代码

既然得到了类型,那么执行这份代码其实毫无压力,因为我们都懂得反射(好吧,我假装你懂反射)。

  1. var transformer = Activator.CreateInstance(type);
  2. var newContent = (string) type.GetMethod("Transform").Invoke(transformer,
  3. new object[] { "某个泛型类的全文,假装我是泛型类 Walterlv<T> is a sb.", 2 });

执行完之后,里面的 Walterlv<T> 真的变成了 Walterlv<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> 啊。说明成功执行。

下面进入高阶模式

作为入门篇,我才不会进入高阶模式呢!如果你想实现如本文开头所说的更通用的效果,欢迎发动你的大脑让想象力迸发。当然,如果你确实想不出来,欢迎在下方评论,我将尽快回复。

参考资料

Roslyn 入门:使用 .NET Core 版本的 Roslyn 编译并执行跨平台的静态的源码的更多相关文章

  1. [C#] .NET Core项目修改project.json来引用其他目录下的源码等文件的办法 & 解决多框架时 project.json 与 app.config冲突的问题

    作者: zyl910 一.缘由 项目规模大了后,经常会出现源码文件分布在不同目录的情况,但.NET Core项目默认只有项目目录下的源码文件,且不支持"Add As Link"方式 ...

  2. .NET Core项目修改project.json来引用其他目录下的源码等文件的办法 & 解决多框架时 project.json 与 app.config冲突的问题

    作者: zyl910 一.缘由 项目规模大了后,经常会出现源码文件分布在不同目录的情况,但.NET Core项目默认只有项目目录下的源码文件,且不支持“Add As Link”方式引入文件.这时需要手 ...

  3. .Net Core 认证系统之基于Identity Server4 Token的JwtToken认证源码解析

    介绍JwtToken认证之前,必须要掌握.Net Core认证系统的核心原理,如果你还不了解,请参考.Net Core 认证组件源码解析,且必须对jwt有基本的了解,如果不知道,请百度.最重要的是你还 ...

  4. cesium1.65api版本贴地贴模型标绘工具效果(附源码下载)

    前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 内 ...

  5. arcgis api 3.x for js 入门开发系列十七在线天地图、百度地图、高德地图(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  6. 4.9版本的linux内核中eeprom存储芯片at24c512的驱动源码在哪里

    答:drivers/misc/eeprom/at24.c,内核配置项为CONFIG_EEPROM_AT24 Location: -> Device Drivers -> Misc devi ...

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

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

  8. Roslyn入门(一)-C#语法分析

    演示环境 Visual Studio 2017 .NET Compiler Platform SDK 简介 今天,Visual Basic和C#编译器是黑盒子:输入文本然后输出字节,编译管道的中间阶段 ...

  9. CYQ.Data 正式支持 DotNET Core 版本发布

    闲话几句: 自从上周开始,IOS人员逝去,就开始接手IOS的代码了. 并开始整理IOS的代码(包括当时一开始设计的开发框架). 在未来不远的日子里,设想是有一个系列详细的介绍I恋App和IT连App及 ...

随机推荐

  1. jQuery实际案例⑥——图片跟随鼠标、五角星评分案例

    一.图片跟随鼠标移动 1.要求:鼠标移动到哪,图片就要跟到哪 2.用到的事件:首先监听鼠标:$(document).mousemove(function(event){ }); //此时可以获取鼠标距 ...

  2. 使用Python操作memcache

    Python连接memcached的库有很多,处于简单以及高效的原则,最终选择了pymemcache, 优点 完全实现了memcached text协议 对于send/recv操作可以配置timeou ...

  3. JQuery获取指定元素中的checkbox选中状态的一些属性

    项目中用户上传病例数据,每一次上传自动生成一个病例文件夹,数据保存到后台,前端显示文件夹,现在的需求是勾选想要删除的文件夹的chenckbox,点击删除后,数据库和前端都相应的更新. 如果是静态页面, ...

  4. 【性能测试】服务器性能监控、数据采集工具nmon安装使用详解

    nmon nmon是一种在AIX与各种Linux操作系统上广泛使用的监控与分析工具,它能在系统运行过程中实时地捕捉系统资源的使用情况,并且能输出结果到文件中,然后通过nmon_analyzer工具产生 ...

  5. Uncaught SyntaxError: Unexpected end of input 突然报了这个错

    最后排查:把 return true 注掉好了,接着在打开注释,依然不报错.最后不报错了.0.0 ~~~

  6. flask学习(六):URL传参

    1. 参数的作用:可以在相同的URL,但是指定不同的参数,来加载不同的数据 例如:简书上每一篇文章前面的URL相同,只是后面的参数不同 2. 在flask中如何使用参数: 注意: 1) 参数需要放在两 ...

  7. 快速切题 poj 1003 hangover 数学观察 难度:0

    Hangover Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 103896   Accepted: 50542 Descr ...

  8. 【51nod-1138】连续整数的和

    本来想着用尺取的思想,不过会超时.利用等差数列S = na+n*n(n-1)/2,得a = (2*S-n*(n-1))/(2*n),然后遍历n,只要满足a是整数就可以,这样复杂度从O(S)变成了O(s ...

  9. 四、dbms_alert(用于生成并传递数据库预警信息)

    1.概述 作用:用于生成并传递数据库预警信息.使用包DBMS_ALERT,则必须以SYS登陆,为该用户授予执行权限.sql>conn sys/oracle as sysdbasql>gra ...

  10. Rsync安装和配置

    一.Rsync简介 1.1什么是Rsync Rsync是一款快速的,开源的,多功能的,可以实现全量和增量的远程和本地的数据同步和数据备份的工具. 全量的概念是:全部备份. 增量的概念是:差异化备份.对 ...