C# Roslyn 编译器Api妙用:动态生成类并实现接口
2021-11-18 12:13
916 查看
在上一篇文章中有讲到使用反射手写IL代码动态生成类并实现接口。
反射的妙用:C#通过反射动态生成类型继承接口并实现
有位网友推荐使用
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的一些简单的使用。
相关文章推荐
- #数据库数据导入导出系列之五 C#实现动态生成Word(转)
- C#编程实现动态生成Word文档
- C#实现动态生成Word
- 前端调取C# api 接口 下载excel,页面上直接下载的那种。看图5手拼模板,动态数据。
- 通过代理接口在内存中动态生成代理类源代码并编译实现的真正动态代理
- C# 动态实现接口
- C#基于QRCode实现动态生成自定义二维码图片功能示例
- C#编程实现动态生成Word文档
- Java 调用C# webservice接口 生成java客户端 实现方式
- C#实现Excel动态生成PivotTable
- 润乾报表 报表组使用api相关接口实现动态添加报表项
- C#实现DataGrid(GridView)动态生成列
- C#.net 动态生成的button按钮及回发处理的实现
- C# 动态生成Excel,可实现冻结窗口等其他Excel扩展
- C#.net 动态生成的button按钮及回发处理的实现
- C# 实现 Aop [Emit动态生成代理类方式]
- C# 动态加载程序集dll (实现接口)
- C#.net 动态生成的button按钮及回发处理的实现
- 用C#实现动态生成Word文档,在Word文档中插入表格,并将读出的数据填入到表格中
- c#语言asp.net实现treeview控件读数据库动态生成树的代码