关于C#的动态类型与动态编译的简介,主要是一个Demo。

动态类型

动态类型的应用场景

  1. 可以减少强制转换(强制转换其实挺好的,让程序猿清楚地指定自己做了什么,不至于出错时不知所措)
  2. 简化反射的写法。
  3. 与动态语言交互。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Install-Package IronPython // 需要安装此 Nuget包
// 引入动态类型之后
// 可以在C#语言中与动态语言进行交互
// 下面演示在C#中使用动态语言Python
ScriptEngine engine = Python.CreateEngine();
// 调用Python语言的print函数来输出
engine.Execute("print 'Hello world'");
// 调用python求解汉罗塔问题
engine.Execute(PythonCode1());
// 调用python进行计算,返回 dynamic 类型
dynamic result = engine.Execute("123+456");
public static string PythonCode1()
{
string code = "count=0\n" +
"def move(n,A,B,C):\n" +
" global count\n" +
" if(n==1):\n" +
" print(A+\"->\"+C)\n" +
" count = count + 1\n" +
" return\n" +
" move(n - 1, A, C, B)\n" +
" move(1, A, B, C)\n" +
" move(n - 1, B, A, C)\n" +
" return\n" +
"move(5,\"A\",\"B\",\"C\")";
return code;
}

动态编译

javascript 和 matlab 等脚本语言会有 eval 这个函数,可以将一些动态生成的字符串作为代码执行,某些情况下很实用。
C#同样支持动态编译。

最主要的两个类:
CodeDomProviderCompilerParameters
前者相当于编译器,后者相当于编译器参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void Test1()
{
CodeDomProvider compiler = new CSharpCodeProvider(); //编译器
CompilerParameters comPara = new CompilerParameters(); //编译器参数
comPara.GenerateExecutable = true; // 生成exe文件
comPara.GenerateInMemory = false; // 是否在内存在输出
comPara.OutputAssembly = "SimpleCompile.exe"; // 输出文件
compiler.CompileAssemblyFromSource(comPara, GetCode1());
// 在当前目录生成 SimpleCompile.exe ,可直接运行
}
public static string GetCode1()
{
string code = @"using System;
class Test
{
static void Main()
{
Console.WriteLine(""Hello world"");
Console.ReadLine();
}
}";
return code;
}

详细下介绍可以看这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static void Test2()
{
CodeDomProvider compiler = new CSharpCodeProvider(); //编译器
CompilerParameters comPara = new CompilerParameters(); //编译器参数
comPara.GenerateExecutable = false;
comPara.GenerateInMemory = true;
// GetCode2() 见文末
CompilerResults compilerResults = compiler.CompileAssemblyFromSource(comPara, GetCode2());
if (compilerResults.Errors.HasErrors)
{
Console.WriteLine("编译错误");
foreach (CompilerError err in compilerResults.Errors)
{
Console.WriteLine(err.ErrorText);
}
return;
}
// 通过反射,调用HelloWorld的实例
Assembly objAssembly = compilerResults.CompiledAssembly;
object objHelloWorld = objAssembly.CreateInstance("DynamicCodeGenerate.HelloWorld");
MethodInfo objMi = objHelloWorld?.GetType().GetMethod("OutPut");
var result = objMi?.Invoke(objHelloWorld, null);
Console.WriteLine(result);
// 动态类型调用
// 可以看到,动态调用比反射调用写法简介不少
dynamic dynObj = objAssembly.CreateInstance("DynamicCodeGenerate.HelloWorld");
var result2 = dynObj?.OutPut();
Console.WriteLine(result2);
}

CSScript

CSScript是C#的一个动态编译引擎。
简介看这里:

如果用原生的动态编译,每次都要生成一个程序集,然后通过反射的方式去调用,过于麻烦。
如果只是想动态编译一句代码,CSScript提供了一种特别方便的写法。

1
2
3
4
5
6
7
var sqr = CSScript.Evaluator
.CreateDelegate(@"int Sqr(int a)
{
return a * a;
}");
var r = sqr(3); // 计算3的平方

使用 CS-Script 需要安装相关Nuget包 (会安装很多东西,依赖项很多)

Install-Package CS-Script

  • Scripting.evaluator.cs
  • Scripting.Extensions.cs
  • Scripting.native.cs

这三个文件是 Nuget 安装 CS-Script 之后自动载入的,里面有很多实用的例子~

有了CSScript,对原有的动态编译的调用也变得简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void Test1()
{
// 得到 Assembly,反射调用
Assembly ass = CSScript.LoadCode(GetCode2()); // GetCode2()见文末
AsmHelper assAsmHelper = new AsmHelper(ass);
object obj = assAsmHelper.CreateObject("DynamicCodeGenerate.HelloWorld");
var method = assAsmHelper.GetMethod(obj, "OutPut");
object result = method.Invoke();
// 动态调用
dynamic obj2 = CSScript.Evaluator.LoadCode(GetCode2());
dynamic result2 = obj2.OutPut();
}

关于CSScript的性能问题,可以参看这里:C#脚本引擎 CS-Script 之(二)——性能评测


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static string GetCode2()
{
StringBuilder sb = new StringBuilder();
sb.Append("using System;");
sb.Append(Environment.NewLine);
sb.Append("namespace DynamicCodeGenerate");
sb.Append(Environment.NewLine);
sb.Append("{");
sb.Append(Environment.NewLine);
sb.Append(" public class HelloWorld");
sb.Append(Environment.NewLine);
sb.Append(" {");
sb.Append(Environment.NewLine);
sb.Append(" public string OutPut()");
sb.Append(Environment.NewLine);
sb.Append(" {");
sb.Append(Environment.NewLine);
sb.Append(" return \"Hello world!\";");
sb.Append(Environment.NewLine);
sb.Append(" }");
sb.Append(Environment.NewLine);
sb.Append(" }");
sb.Append(Environment.NewLine);
sb.Append("}");
string code = sb.ToString();
return code;
}