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

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


本文是 Roslyn 入门系列之一:

  • Roslyn 入门:使用 Visual Studio 的语法可视化(Syntax Visualizer)窗格查看和了解代码的语法树
  • Roslyn 入门:使用 .NET Core 版本的 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 包足矣:

  • Microsoft.CodeAnalysis.CSharp

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

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

using System.Linq;
using static System.Environment;namespace Walterlv.Demo.Roslyn
{public class GenericGenerator{private static readonly string GeneratedAttribute =@"[System.CodeDom.Compiler.GeneratedCode(""walterlv"", ""1.0"")]";public string Transform(string originalCode, int genericCount){if (genericCount == 1){return originalCode;}var content = originalCode// 替换泛型。.Replace("<out T>", FromTemplate("<{0}>", "out T{n}", ", ", genericCount)).Replace("Task<T>", FromTemplate("Task<({0})>", "T{n}", ", ", genericCount)).Replace("Func<T, Task>", FromTemplate("Func<{0}, Task>", "T{n}", ", ", genericCount)).Replace(" T, Task>", FromTemplate(" {0}, Task>", "T{n}", ", ", genericCount)).Replace("(T, bool", FromTemplate("({0}, bool", "T{n}", ", ", genericCount)).Replace("var (t, ", FromTemplate("var ({0}, ", "t{n}", ", ", genericCount)).Replace(", t)", FromTemplate(", {0})", "t{n}", ", ", genericCount)).Replace("return (t, ", FromTemplate("return ({0}, ", "t{n}", ", ", genericCount)).Replace("<T>", FromTemplate("<{0}>", "T{n}", ", ", genericCount)).Replace("(T value)", FromTemplate("(({0}) value)", "T{n}", ", ", genericCount)).Replace("(T t)", FromTemplate("({0})", "T{n} t{n}", ", ", genericCount)).Replace("(t)", FromTemplate("({0})", "t{n}", ", ", genericCount)).Replace("var t =", FromTemplate("var ({0}) =", "t{n}", ", ", genericCount)).Replace(" T ", FromTemplate(" ({0}) ", "T{n}", ", ", genericCount)).Replace(" t;", FromTemplate(" ({0});", "t{n}", ", ", genericCount))// 生成 [GeneratedCode]。.Replace("    public interface ", $"    {GeneratedAttribute}{NewLine}    public interface ").Replace("    public class ", $"    {GeneratedAttribute}{NewLine}    public class ").Replace("    public sealed class ", $"    {GeneratedAttribute}{NewLine}    public sealed class ");return content.Trim();}private static string FromTemplate(string template, string part, string seperator, int count){return string.Format(template,string.Join(separator, Enumerable.Range(1, count).Select(x => part.Replace("{n}", x.ToString()))));}}
}

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

编译这份代码

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

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

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

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;namespace Walterlv.Demo.Roslyn
{class Program{static void Main(string[] args){// 大家都知道在代码中写死文件路径是不对的,不过,我们这里是试验。放心,我会改的!var file = @"D:\Development\Demo\Walterlv.Demo.Roslyn\Walterlv.Demo.Roslyn.Tests\GenericGenerator.cs";var originalText = File.ReadAllText(file);var syntaxTree = CSharpSyntaxTree.ParseText(originalText);var type = CompileType("GenericGenerator", syntaxTree);// 于是我们得到了编译后的类型,但是还不知道怎么办。}private static Type CompileType(string originalClassName, SyntaxTree syntaxTree){// 指定编译选项。var assemblyName = $"{originalClassName}.g";var compilation = CSharpCompilation.Create(assemblyName, new[] { syntaxTree },options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)).AddReferences(// 这算是偷懒了吗?我把 .NET Core 运行时用到的那些引用都加入到引用了。// 加入引用是必要的,不然连 object 类型都是没有的,肯定编译不通过。AppDomain.CurrentDomain.GetAssemblies().Select(x => MetadataReference.CreateFromFile(x.Location)));// 编译到内存流中。using (var ms = new MemoryStream()){var result = compilation.Emit(ms);if (result.Success){ms.Seek(0, SeekOrigin.Begin);var assembly = Assembly.Load(ms.ToArray());return assembly.GetTypes().First(x => x.Name == originalClassName);}throw new CompilingException(result.Diagnostics);}}}
}

执行编译后的代码

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

var transformer = Activator.CreateInstance(type);
var newContent = (string) type.GetMethod("Transform").Invoke(transformer,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> 啊。说明成功执行。

下面进入高阶模式

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

参考资料

  • Compiling C# Code Into Memory and Executing It with Roslyn - Tugberk Ugurlu’s Blog

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

  1. 深入理解Spark 2.1 Core (十):Shuffle Map 端的原理与源码分析

    在上一篇<深入理解Spark 2.1 Core (九):迭代计算和Shuffle的原理与源码分析>提到经过迭代计算后, SortShuffleWriter.write中: // 根据排序方 ...

  2. 深入理解Spark 2.1 Core (五):Standalone模式运行的原理与源码分析

    概述 前几篇博文都在介绍Spark的调度,这篇博文我们从更加宏观的调度看Spark,讲讲Spark的部署模式.Spark部署模式分以下几种: local 模式 local-cluster 模式 Sta ...

  3. 小程序源码:全网独家小程序版本独立微信社群人脉系统社群空间站最新源码开源+详细教程

    功能介绍: 1.微信社群是一个集发布.展示社群信息.人脉推广的裂变工具/平台. 2.通过人脉广场,将商家信息通过名片进行展示,让资源对接.人脉推广更加便捷高效.为平台带来更多流量,让平台更有价值. 3 ...

  4. 2022年最新视觉框架VM PRO 2.7版本,增加了机器人 流程框架 多任务流程 C#源码框架,机器视觉源码框架

    2022年最新视觉框架VM PRO 2.7版本,增加了机器人 流程框架 多任务流程 C#源码框架,机器视觉源码框架,编程语言C#,算法使用的是halcon,参考了cognex visionpro的输入 ...

  5. 2022年最新视觉框架VM PRO 2.7版本,增加了机器人 流程框架 多任务流程 C#源码框架,机器视觉源码框架,编程语言C#,算法使用的是halcon,参考了cognex visionpro的输入

    2022年最新视觉框架VM PRO 2.7版本,增加了机器人 流程框架 多任务流程 C#源码框架,机器视觉源码框架,编程语言C#,算法使用的是halcon,参考了cognex visionpro的输入 ...

  6. 框架VM PRO 2.7版本,增加了机器人 流程框架 多任务流程 C#源码框架

    2022年最新视觉框架VM PRO 2.7版本,增加了机器人 流程框架 多任务流程 C#源码框架,机器视觉源码框架,编程语言C#,算法使用的是halcon,参考了cognex visionpro的输入 ...

  7. 全网独家3.1.2版本独立微信社群人脉系统社群空间站最新源码开源+详细教程

    最新3.1.2版本独立微信社群人脉系统社群空间站最新源码开源+详细教程 3.1.0旧版本传送门:人脉系统3.1.0 3.1.1旧版本传送门:人脉系统3.1.1 功能介绍: 1.微信社群是一个集发布.展 ...

  8. 最新3.1.1版本独立微信社群人脉系统社群空间站最新源码开源+详细教程

    功能介绍: 1.微信社群是一个集发布.展示社群信息.人脉推广的裂变工具/平台. 2.通过人脉广场,将商家信息通过名片进行展示,让资源对接.人脉推广更加便捷高效.为平台带来更多流量,让平台更有价值. 3 ...

  9. pyTorch入门(六)——实战Android Minist OpenCV手写数字识别(附源码地址)

    学更好的别人, 做更好的自己. --<微卡智享> 本文长度为4239字,预计阅读12分钟 前言 前面几篇文章实现了pyTorch训练模型,然后在Windows平台用C++ OpenCV D ...

  10. 深入理解Spark 2.1 Core (十一):Shuffle Reduce 端的原理与源码分析

    我们曾经在<深入理解Spark 2.1 Core (一):RDD的原理与源码分析 >讲解过: 为了有效地实现容错,RDD提供了一种高度受限的共享内存,即RDD是只读的,并且只能通过其他RD ...

最新文章

  1. rpm包管理功能全解
  2. 汇编语言 pushf 和 popf指令
  3. 【Thymeleaf】获取绝对路径
  4. C#下实现动态系统托盘图标
  5. mysql Slave is not configured or failed to initialize properly. You must at least set --server-id
  6. spring Boot环境下dubbo+zookeeper的一个基础讲解与示例
  7. slf4j和log4j的使用
  8. 查看python版本命令_Anaconda常用命令小结
  9. scla-基础-函数-元组(0)
  10. C++-Qt【1】-退出程序静态调试
  11. 面试常问点:深入剖析JVM的那些事
  12. T9社区注册流程记录(笔记)
  13. jpg和tif转pdf遇到的问题和解决方法
  14. 格式工厂采样率,比特率怎样设置才能使音频声音大容量小
  15. 用Python写糖豆人小游戏 你学“废”了么?
  16. 【零基础Eviews实例】02自相关(序列相关)的检验与修正
  17. 全志T507系统烧录配置
  18. Win11网络延迟太高怎么办?
  19. python3连接Oracle数据库
  20. MyCat实现MySQL读写分离(双主双从多库)

热门文章

  1. c语言ll 1 语法分析器,LL(1)语法分析器的设计与实现
  2. 写给小白的网站优化初步全过程
  3. 使用ffmpeg破解m3u8加密视频文件
  4. leapftp,如何实现leapftp download
  5. js判断ie11和qq,opera浏览器(亲测)
  6. 【烈日炎炎战后端】JAVA多线程(11.2万字)
  7. DeBank和非小号网站的数据分析-实习工作小结
  8. 制作学术PPT的注意事项如何制作模板(附模板下载链接)
  9. ssm房屋中介管理系统毕业设计(附源码、运行环境)
  10. 总结--上传插件Upload.js的使用