Roslyn 编译器Api妙用:动态生成类并实现接口
在上一篇文章中有讲到使用反射手写IL代码动态生成类并实现接口。
有位网友推荐使用 Roslyn 去脚本化动态生成,今天这篇文章就主要讲怎么使用 Roslyn 动态生成类。

什么是Roslyn
最初 C# 语言的编译器是用 C++ 编写的,后来微软推出了一个新的用 C# 自身编写的编译器:Roslyn,它属于自举编译器。
所谓自举编译器就是指,某种编程语言的编译器就是用该语言自身来编写的。自举编译器的每个版本都是用该版本之前的版本来编译的,但它的第一个版本必须由其它语言编写的编译器来编译,比如 Roslyn 的第一个版本是由 C++ 编写的编译器来编译的。很多编程语言发展成熟后都会用该语言本身来编写自己的编译器,比如 C# 和 Go 语言。
在 .NET 平台,Roslyn 编译器负责将 C# 和 VB 代码编译为程序集。
大多数现有的传统编译器都是“黑盒”模式,它们将源代码转换成可执行文件或库文件,中间发生了什么我们无法知道。与之不同的是,Roslyn 允许你通过 API 访问代码编译过程中的每个阶段。
以上内容取自:精致码农 • 王亮
Roslyn实现拦截器
拦截器用过MVC的应该都很熟悉:Filter,比如,请求拦截器:OnActionExecuting、OnActionExecuted。
下面就用Roslyn简单实现类似:OnActionExecuting 的拦截效果。
1、先准备一段拦截脚本
const string before = "public static string before(string name)" +
"{" +
"Console.WriteLine($\"{name}已被拦截,形成检测成功,无感染风险。\");" +
"return name+\":健康\";" +
"}";
2、准备传参对象
public class ParameterVector
{
public string arg1 { get; set; }
}
3、编写脚本执行代码
/// <summary>
/// 执行Before脚本
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static string ExecuteBeforeScript(string name)
{
StringBuilder builder = new StringBuilder();
builder.Append("public class intercept");
builder.Append("{");
builder.Append(before);
builder.Append("}");
builder.Append("return intercept.before(arg1);");
var result = CS.CSharpScript.RunAsync<string>(builder.ToString(),
// 引用命名空间
ScriptOptions.Default.AddReferences("System.Linq").AddImports("System"),
// 参数对象
globals: new ParameterVector() { arg1 = name },
globalsType: typeof(ParameterVector))
.Result;
return result.ReturnValue;
}
4、调用拦截器
static void Main(string[] args)
{
var msg = Console.ReadLine();
travel(msg);
Console.WriteLine("执行完毕...");
}
static string travel(string userName)
{
var result = Script.ExecuteBeforeScript(userName);
Console.WriteLine(result);
return result;
}

咋一看上面的逻辑其实很傻,就是将方法逻辑写成静态脚本去动态调用。还不如直接就在方法内部写相关逻辑。
但是我们将思维发散一下,将静态脚本替换为从文件读取,在业务上线后,我们只需要修改文件脚本的逻辑即可,是不是觉得用处就来了,是不是有那么点 AOP 的感觉了。
Roslyn动态实现接口
下面的内容与之前反射动态生成的结果一样,只是换了一种方法去处理。
1、准备需要实现的接口,老User了
public interface IUser
{
string getName(string name);
}
2、准备一个拦截类
public class Intercept
{
public static void Before(string name)
{
Console.WriteLine($"拦截成功,参数:{name}");
}
}
3、根据接口生成一个静态脚本
/// <summary>
/// 生成静态脚本
/// </summary>
/// <typeparam name="Tinteface"></typeparam>
/// <returns></returns>
public static string GeneratorScript<Tinteface>(string typeName)
{
var t = typeof(Tinteface);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("using System;");
stringBuilder.Append($"using {t.Namespace};");
stringBuilder.Append($"namespace {typeName}_namespace");
stringBuilder.Append("{");
stringBuilder.Append($"public class {typeName}:{t.Name}");
stringBuilder.Append(" {");
MethodInfo[] targetMethods = t.GetMethods();
foreach (MethodInfo targetMethod in targetMethods)
{
if (targetMethod.IsPublic)
{
var returnType = targetMethod.ReturnType;
var parameters = targetMethod.GetParameters();
string pStr = string.Empty;
List<string> parametersName = new List<string>();
foreach (ParameterInfo parameterInfo in parameters)
{
var pType = parameterInfo.ParameterType;
pStr += $"{pType.Name} _{pType.Name},";
parametersName.Add($"_{pType.Name}");
}
stringBuilder.Append($"public {returnType.Name} {targetMethod.Name}({pStr.TrimEnd(',')})");
stringBuilder.Append(" {");
foreach (var pName in parametersName)
{
stringBuilder.Append($"Intercept.Before({pName});");
}
stringBuilder.Append($"return \"执行成功。\";");
stringBuilder.Append(" }");
}
}
stringBuilder.Append(" }");
stringBuilder.Append(" }");
return stringBuilder.ToString();
}
4、构建程序集
/// <summary>
/// 构建类对象
/// </summary>
/// <typeparam name="Tinteface"></typeparam>
/// <returns></returns>
public static Type BuildType<Tinteface>()
{
var typeName = "_" + typeof(Tinteface).Name;
var text = GeneratorTypeCode<Tinteface>(typeName);
// 将代码解析成语法树
SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(text);
var objRefe = MetadataReference.CreateFromFile(typeof(Object).Assembly.Location);
var consoleRefe = MetadataReference.CreateFromFile(typeof(IUser).Assembly.Location);
var compilation = CSharpCompilation.Create(
syntaxTrees: new[] { tree },
assemblyName: $"assembly{typeName}.dll",
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary),
references: AppDomain.CurrentDomain.GetAssemblies().Select(x => MetadataReference.CreateFromFile(x.Location)));
Assembly compiledAssembly;
using (var stream = new MemoryStream())
{
// 检测脚本代码是否有误
var compileResult = compilation.Emit(stream);
compiledAssembly = Assembly.Load(stream.GetBuffer());
}
return compiledAssembly.GetTypes().FirstOrDefault(c => c.Name == typeName);
}
5、调用动态生成类的方法
static void Main(string[] args)
{
Type t = codeExtension.BuildType<IUser>();
var method = t.GetMethod("getName");
object obj = Activator.CreateInstance(t);
var result = method.Invoke(obj, new object[] { "张三" }).ToString();
Console.WriteLine(result);
}

两种(Roslyn/IL)动态生成方式比起来,从编码方式比起来差别还是挺大的。
手写IL无疑要比Roslyn复杂很多,手写IL无法调试,无法直观展示代码,没有错误提示,如果业务逻辑比较复杂将会是一场灾难。Roslyn将业务逻辑脚本化,代码通过脚本可直观展示,有明确的错误提示。
至于性能方面暂时还没有做比较,后续有机会再将两种方式的性能对比放出来。
Roslyn异常提示
上面的代码中,有一小段代码:
// 检测脚本代码是否有误
var compileResult = compilation.Emit(stream);
脚本无误的返回值如下:

当脚本出现错误的返回值如下:

从上面的错误中很明显可以看到,缺少了 System 命名空间,以及方法签名与接口不匹配。
以上就是Roslyn编译器Api的一些简单的使用。
Roslyn 编译器Api妙用:动态生成类并实现接口的更多相关文章
- 【原】如何获取Java动态生成类?
写作目的:Java大部分框架,如Spring,Hibernate等都会利用动态代理在程序运行的时候生成新的类, 有的时候为了学习,或者深入了解动态代理,想查看动态生成类的源代码究竟长怎么个样子, 通过 ...
- Java动态生成类以及动态添加属性
有个技术实现需求:动态生成类,其中类中的属性来自参数对象中的全部属性以及来自参数对象properties文件. 那么技术实现支持:使用CGLib代理. 具体的实现步骤: 1.配置Maven文件: &l ...
- JDK 工具 HSDB 查看动态生成类
前置工作 1. 复制 JDK 安装目录\jre\bin\sawindbg.dll 到 JDK 安装目录同级的 jre\bin 目录下,否则会报错找不到 sawindbg.dll 文件. 比如我的 sa ...
- java ASM动态生成类
BeanTest2.java import java.io.FileOutputStream; import org.objectweb.asm.AnnotationVisitor; import o ...
- 动态生成简约MVC请求接口|抛弃一切注解减少重复劳动吧
背景 目前创建一个后端请求接口给别人提供服务,无论是使用SpringMVC方式注解,还是使用SpringCloud的Feign注解,都是需要填写好@RequestMap.@Controller.@Pa ...
- C# 动态生成类 枚举等
private void GenerateCode() { /*注意,先导入下面的命名空间 using System.CodeDom using System.CodeDom.Compiler; us ...
- 动态We API层(动态生成js)
ABP动态webapi前端怎么调用? 研究abp项目时,页面js文件中一直不明白abp.services... 是从哪里来的 在调试SimpleTaskSystem的AngularJs demo时,一 ...
- java 动态生成类再编译最后代理
package spring.vhostall.com.proxy; public interface Store { public void sell(); } ------------------ ...
- JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架
1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...
随机推荐
- 【Azure Developer】Azure Automation 自动化账号生成的时候怎么生成连接 与证书 (Connection & Certificate)
Azure Automation :The Azure Automation service provides a highly reliable and scalable workflow exec ...
- 洛谷5024 保卫王国 (动态dp)
qwq非正解. 但是能跑过. 1e5 log方还是很稳的啊 首先,考虑最普通的\(dp\) 令\(dp1[x][0]表示不选这个点,dp1[x][1]表示选这个点的最大最小花费\) 那么 \(dp1[ ...
- 洛谷2387 NOI2014魔法森林(LCT维护最小生成树)
本题是运用LCT来维护一个最小生成树. 是一个经典的套路 题目中求的是一个\(max(a_i)+max(b_i)\)尽可能小的路径. 那么这种的一个套路就是,先按照一维来排序,然后用LCT维护另一维 ...
- bzoj3073Journeys(线段树优化最短路)
这里还是一道涉及到区间连边的问题. 如果暴力去做,那么就会爆炸 那么这时候就需要线段树来优化了. 因为是双向边 所以需要两颗线段树来分别对应入边和出边 QwQ然后做就好了咯 不过需要注意的是,这个边数 ...
- HTML5元素背景知识
目录 HTML5元素背景知识 语义与呈现分离 元素选用原则 少亦可为多 别误用元素 具体为佳,一以贯之 对用户不要想当然 元素说明体例 ol元素 元素速览 文档和元数据元素 文档和元数据元素 文本元素 ...
- Less-(26~28) preg_replace3
Less-26: 核心语句: 各种回显均存在. 本题相比Less-25,多屏蔽了很多符号: 首先是各种注释符 --+,#,/**/ . /[]/表示字符集合:任何出现在里面的字符均会被替换. 屏蔽 ...
- Elasticsearch 中为什么选择倒排索引而不选择 B 树索引
目录 前言 为什么全文索引不使用 B+ 树进行存储 全文检索 正排索引 倒排索引 倒排索引如何存储数据 FOR 压缩 RBM 压缩 倒排索引如何存储 字典树(Tria Tree) FST FSM 构建 ...
- 不可错过的stm32单片机直流电机驱动与测速详解
stm32直流电机驱动与测速 说实话就现在的市场应用中stm32已经占到了绝对住到的地位,51已经成为过去式,32的功能更加强大,虽然相应的难度有所增加,但是依然阻止不了大家学习32的脚步,不说大话了 ...
- 你一定不知道的Unsafe用法
Unsafe是什么 首先我们说Unsafe类位于rt.jar里面sun.misc包下面,Unsafe翻译过来是不安全的,这倒不是说这个类是不安全的,而是说开发人员使用Unsafe是不安全的,也就是不推 ...
- AOP源码解析:AspectJAwareAdvisorAutoProxyCreator类的介绍
AspectJAwareAdvisorAutoProxyCreator 的类图 上图中一些 类/接口 的介绍: AspectJAwareAdvisorAutoProxyCreator : 公开了Asp ...