前言

从 C# 6.0 开始,C# 编译器就从以前由 C++ 实现的 csc.exe 换成了用 C# 重新实现的开放式 API 式编译服务 Roslyn。这个编译器到现在已经替代了老式编译器,从前 WebForm 的动态编译基础 CSharpCodeProvider 就是对 csc.exe 的调用包装,当然也继承了 csc.exe 的所有毛病。比如这是一个完全的黑盒编译器,把源码传进去返回程序集,中间的符号解析,词法、语法分析,优化和生成这些中间步骤究竟发生了什么我们无从知晓。无法作为脚本引擎把 C# 代码作为脚本执行,曾经有过第三方脚本引擎。当然还有不支持 C# 6.0 或更高版本源代码的编译,巨硬是铁了心要强推 Roslyn 逼死 csc.exe 啊,虽然都 9102 年了,csc.exe 早都透心凉了。

到 VS 2019,C# 又多了一个功能,导航到反编译的源代码,虽然之前已经有各种第三方反编译工具和 VS 集成扩展,但总归没有自带的来的方便。之前大名鼎鼎的 dotPeek 也在使用中发现无法反编译框架为 .NetStandard 2.1 和 .NetCoreApp 3.1 的程序集,不知道现在怎么样了。刚好在使用中发现了 “由 ICSharpCode.Decompiler 提供反编译服务”字样,抱着好玩的心态去 Nuget 搜索了一下,居然真的找到了!既然这样,那岂不是能:把 Roslyn 编译的程序集交给 ICSharpCode.Decompiler 反编译的源代码交给 Roslyn 编译的程序集交给 ICSharpCode.Decompiler 反编译的源代码交给…… (→_→)

正文

这两个包的基本使用其实很简单。在项目的管理 Nuget 程序包中搜索并安装 ICSharpCode.Decompiler 和 Microsoft.CodeAnalysis.CSharp.Scripting。

1、Microsoft.CodeAnalysis.CSharp.Scripting 基本使用:

引用命名空间 using Microsoft.CodeAnalysis.CSharp.Scripting;

开始使用:

 //执行表达式,返回强类型结果
int result = await CSharpScript.EvaluateAsync<int>("1 + 2");

就这么简单,已经能执行一个简单的表达式了,连 C# 语句要分号这种规则都不用 \(@^0^@)/

其他各种花式用法:要引用更多命名空间,包括但不限于

 using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
             //处理编译错误
try
{
Console.WriteLine(await CSharpScript.EvaluateAsync("2+"));
}
catch (CompilationErrorException e)
{
Console.WriteLine("2+ : " + string.Join(Environment.NewLine, e.Diagnostics));
} //添加程序集引用
var result1 = await CSharpScript.EvaluateAsync("System.Net.Dns.GetHostName()",
ScriptOptions.Default.WithReferences(typeof(System.Net.Dns).Assembly));
Console.WriteLine("System.Net.Dns.GetHostName() : " + result1); //导入命名空间 using
var result2 = await CSharpScript.EvaluateAsync("Directory.GetCurrentDirectory()",
ScriptOptions.Default.WithImports("System.IO"));
Console.WriteLine("Directory.GetCurrentDirectory() : " + result2); //导入静态类型 using static
var result3 = await CSharpScript.EvaluateAsync("Sqrt(2)",
ScriptOptions.Default.WithImports("System.Math"));
Console.WriteLine("Sqrt(2) : " + result3); //参数化脚本
var globals = new Globals {X = , Y = };
Console.WriteLine("X + Y : " + await CSharpScript.EvaluateAsync<int>("X+Y", globals: globals)); //编译缓存并多次执行脚本
var script = CSharpScript.Create<int>("X*Y", globalsType: typeof(Globals));
script.Compile();
for (int i = ; i < ; i++)
{
Console.WriteLine("No " + (i + ) + " X*Y : " + (await script.RunAsync(new Globals { X = i, Y = i })).ReturnValue);
} //编译脚本为委托
script = CSharpScript.Create<int>("X/Y", globalsType: typeof(Globals));
ScriptRunner<int> runner = script.CreateDelegate();
for (int i = ; i < ; i++)
{
Console.WriteLine("No " + (i + ) + " X/Y : " + await runner(new Globals { X = new Random().Next(,i), Y = new Random().Next(, i) }));
} //运行脚本片段并检查已定义的变量
var state = await CSharpScript.RunAsync<int>("int answer = 42;");
foreach (var variable in state.Variables)
Console.WriteLine($"{variable.Name} = {variable.Value} of type {variable.Type}"); //连接多个片段为一个脚本
var script1 = CSharpScript.
Create<int>("int x = 1;").
ContinueWith("int y = 2;").
ContinueWith("x + y");
Console.WriteLine("x + y : " + (await script1.RunAsync()).ReturnValue);
//获取编译对象以访问所有Roslyn API
//var compilation = script1.GetCompilation(); //从之前的状态继续执行脚本
var state1 = await CSharpScript.RunAsync("int x = 1;");
state1 = await state1.ContinueWithAsync("int y = 2;");
state1 = await state1.ContinueWithAsync("x+y");
Console.WriteLine("x + y : " + state1.ReturnValue); //读取代码文件并执行编译
var file = @"C:\Users\Administrator\source\repos\ConsoleApp2\ConsoleApp2/GenericGenerator.cs";
var originalText = File.ReadAllText(file);
var syntaxTree = CSharpSyntaxTree.ParseText(originalText);//获取语法树
var type = CompileType("GenericGenerator", syntaxTree);//执行编译并获取类型 var transformer = Activator.CreateInstance(type);
var newContent = (string)type.GetMethod("Transform").Invoke(transformer,
new object[] { "某个泛型类的全文,假装我是泛型类 Walterlv<T> is a sb.", }); var aa = new GenericGenerator();
var str = aa.Transform("某个泛型类的全文,假装我是泛型类 Walterlv<T> is a sb.", );
         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().Where(x=>!string.IsNullOrEmpty(x.Location)).Select(x => MetadataReference.CreateFromFile(x.Location))); // 编译到内存流中。
using (var ms = new MemoryStream())
{
var result = compilation.Emit(ms); if (result.Success)
{
ms.Seek(, SeekOrigin.Begin);
var assembly = Assembly.Load(ms.ToArray());
return assembly.GetTypes().First(x => x.Name == originalClassName);
}
throw new Exception(string.Join(Environment.NewLine, result.Diagnostics));
}
}

当然还有这种四不像代码也是可以滴

 //不仅可以在脚本引擎中引用命名空间,还可以在脚本中引用,当然前提是在脚本引擎中引用了相关程序集
using System; //没有 namespace Xxx { /* code */ } 的定义,直接在外面定义一个类
public class Point
{
public int X {get; set;}
public int Y {get; set;} public Point(int x, int y)
{
X = x;
Y = y;
} public override string ToString()
{
return $"({X}, {Y})";
}
} //变量没有定义在任何类或类的方法中,正常的 C# 代码一定会报错,但骚断腿的 CSharpScript.EvaluateAsync<TResult>(string code) 能正常执行
var random = new Random(); int a = random.Next(, );
int b = random.Next(, );
Func<int, int, int> plus = (l, r) => l + r;
//最后这个表达式没有分号结尾,也没有使用变量接收结果。当然,最后这个带返回值的表达式的结果会在 string result = await CSharpScript.EvaluateAsync<string>(code); 的 result 里收到
$"{a}+{b}={plus(a, b)}\r\nPoint : {new Point(random.Next(0, 1000), random.Next(0, 1000))}"

2、ICSharpCode.Decompiler 的基本使用

 using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.TypeSystem; 5 var decompiler = new CSharpDecompiler(typeof(string).Assembly.Location, new DecompilerSettings());
6 var name = new FullTypeName(typeof(string).FullName);
7 var code = decompiler.DecompileTypeAsString(name);

ICSharpCode.Decompiler 的最新版 6.0.1 预览版1 支持最新框架的程序集反编译,同时支持了大多数 C# 特性,包括部分C# 8.0 刚出的新特性。

现在的 .Net Core 真是可以玩出各种骚操作,太好玩了。当然,在下面的演示代码中有一个集成在网站项目的演示,下载后运行 IdentityServer 项目并访问 /OnlineCode/CSharp 和 /DeCompiler/CSharp 就可以查看效果。C# 脚本大多数演示代码在 CSharpScriptDemo 项目中。

效果预览

脚本运行

反编译代码

转载请完整保留以下内容,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!

  本文地址:https://www.cnblogs.com/coredx/p/12045104.html

  完整源代码:Github

  里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。

C# 编译器 和 反编译器,你要哪个(歪头)? 我全都要(捏拳)!的更多相关文章

  1. 预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反)转

    vs2010的mfc项目中编译c语言出现错误: "...预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反)" 解决方法: 建工程时 建立空项目  ...

  2. gcc编译器参数

    [gcc编译步骤] 1.预处理,生成.i的文件[预处理器cpp] 2.将预处理后的文件转换成汇编语言,生成文件.s[编译器egcs] 3.由汇编变为目标代码(机器代码)生成.o的文件[汇编器as] 4 ...

  3. Tiny语言编译器简单介绍

    1.简单介绍:编译器是将一种语言翻译成还有一种语言的程序.编译器将源程序的代码作为输出,从而产生用目标语言编写的等价程序.比如源码为C/C++等高级语言,那么目标语言就是目标机器的机器代码.也就是能够 ...

  4. gcc编译器常用选项的含义

    -w: 关闭编译时的警告, 也就是编译后不显示任何warning,因此有时编译中会出现一些诸如数据转换之类的可忽略警告, -Wall: 显示编译后所有警告 -W: 显示警告,但是只是显示编译器认为的会 ...

  5. Asp.Net Core Identity 骚断腿的究极魔改实体类

    前言 默认的 Identity 实体类型在大多数时候已经基本够用,很多时候也只是稍微在 IdentityUser 类中增加一些自定义数据字段,比如头像.这次,我要向园友隆重介绍我魔改之后的 Ident ...

  6. Smali语法编程

    Smali背景: Smali,Baksmali分别是指安卓系统里的Java虚拟机(Dalvik)所使用的一种.dex格式文件的汇编器,反汇编器.其语法是一种宽松式的Jasmin/dedexer语法,而 ...

  7. 一切从IL开始

    IL是什么? IL是Intermediate Language的缩写,是.Net代码转化成机器语言的一个中间语言,因此又把IL语言称之为反汇编语言. IL工具有哪些? 俗话说,工欲善其事必先利其器.了 ...

  8. Android ===smail语法总结

    (转载自 网络)smail 语法总结 http://www.blogjava.net/midea0978/archive/2012/01/04/367847.html Smali背景: Smali,B ...

  9. 20145209刘一阳《JAVA程序设计》第一周课堂测试

    第一周课堂测试 1.下列不属于Java后继技术的是(D) A .Android B .JSP C .XML D .Python 2.下列关于Java语言特点的描述,正确的一组是(C) A .面向过程: ...

随机推荐

  1. php curl模拟post请求的例子

    curl 在php中要模拟post请求数据提交我们会使用到curl函数,下面我来给大家举几个curl模拟post请求提交数据例子有需要的朋友可参考参考. 注意:curl函数在php中默认是不被支持的, ...

  2. django-自定义文件上传存储类

    文件储存API:https://yiyibooks.cn/xx/django_182/ref/files/storage.html 编写自定义存储系统:https://yiyibooks.cn/xx/ ...

  3. CDN工作机制

    CDN(content delivery network),即内容分布网络,是一种构建在现有Internet上的一种先进的流量分配网络.CDN以缓存网站中的静态数据为主,当用户请求动态内容时,先从CD ...

  4. 抓取Dump文件的方法和工具介绍

    一.Windows系统的任务管理器里抓dump 启动任务管理器,选中某个进程,右键,弹出菜单"创建转储文件" 注意事项: 当你在64位Windows系统上抓32位进程的dmup文件 ...

  5. 【批处理】choice命令,call 命令,start 命令,rem

    [1]choice命令简介 使用此命令可以提示用户输入一个选择项,根据用户输入的选择项再决定执行具体的过程. 使用时应该加/c:参数,c: 后应写提示可输入的字符或数字,之间无空格.冒号是可选项. 使 ...

  6. 树组件——jstree使用

    本文记录的只是我自己当时的代码,每行的注释很清楚了,你自己可以做相应变通 一.使用前提: 1.下载jstree依赖包 2.相关页面引入样式["jstree/themes/default/st ...

  7. 洛谷 P4017 最大食物链计数 题解

    P4017 最大食物链计数 题目背景 你知道食物链吗?Delia生物考试的时候,数食物链条数的题目全都错了,因为她总是重复数了几条或漏掉了几条.于是她来就来求助你,然而你也不会啊!写一个程序来帮帮她吧 ...

  8. 数据分析师(Data Analyst),数据工程师(Data Engineer),数据科学家(Data Scientist)的区别

    数据分析师(Data Analyst):负责从数据中提取出有用的信息,以帮助公司形成业务决策.工作内容包括:对数据进行提取,清洗,分析(用描述统计量,趋势分析,多维度分析,假设检验等统计常用方法对数据 ...

  9. Tkinter 之文件管理器

    一.效果图 二.功能描述 1.打开文件菜单中的打开按钮,可以选择目录. 2.可以查看各种类型的图片. 3.可以编辑文本. 4.显示行号功能,可改变目录显示的宽度. 三.使用的标签 1.Menu 2.F ...

  10. FormData实现文件上传

    我用到FormData传输的使用场景:vant UI组件里面 的图片上传这块,需要调用后台的图片上传接口,使用的是FormData方式上传的 https://www.cnblogs.com/hutuz ...