RunSharp - Reflection.Emit 从未如此简单






4.92/5 (47投票s)
RunSharp (或 Run#) 是 Reflection.Emit API 的一个高级包装器,它允许您快速轻松地在运行时生成代码。
引言
许多开发人员已经发现了使用 Reflection.Emit
在运行时发出代码的魔力。同样,许多人因为 API 的复杂性而未使用它。RunSharp
(或者您更喜欢的 Run#),旨在将 C# 等高级语言的简洁性和可读性引入运行时代码生成。
IL 是一种很棒的语言。但是,直接发出 IL 指令存在几个问题。其中之一是,仅通过查看一系列 Emit()
调用,很难看到生成代码的实际结构。此外,除非您希望收到可怕的“公共语言运行时检测到无效程序”异常,否则在可以发出的 IL 序列方面存在严格的规则。有时,很难确切找出实现某个简单 C# 概念需要什么 IL。还有更多。我创建了 RunSharp
来避免这些问题,并且在编写它的过程中获得了极大的乐趣。我希望您在使用它时也能获得极大的乐趣。
Using the Code
在您开始生成下一个伟大的程序集之前,您只需要了解很少的知识。以下是您在运行时生成第一个“Hello World”的简单步骤:
1. 创建一个程序集
AssemblyGen ag = new AssemblyGen("HelloWorld.exe");
此调用在后台简单地创建 AssemblyBuilder
和 ModuleBuilder
。
2. 定义您的第一个类
TypeGen MyClass = ag.Public.Class("MyClass");
{
// we'll continue here
}
正如您所见,这与您在 C# 中编写的内容非常相似,例如:
public class MyClass
{
// we'll continue here
}
您还可以为类使用其他修饰符,例如 Private
、Sealed
、Abstract
或 NoBeforeFieldInit
(后者允许在访问任何类成员之前或之后调用 static
构造函数 - 有关 beforefieldinit
的一些详细信息,请参阅 Jon Skeet 撰写的精彩 文章)。
3. 定义 Main() 方法
CodeGen g = MyClass.Public.Static.Method(
typeof(void), "Main", typeof(string[]));
{
Operand args = g.Arg(0, "args");
// we'll continue here
}
上面的代码相当于 C# 中的以下内容:
public static void Main(string[] args)
{
// we'll continue here
}
请注意这里 Operand
变量的定义。Operand
是 RunSharp
中的核心类之一,代表字面量、参数、局部变量、表达式等。它还重载了所有运算符,这些运算符可用于构建表达式,就像您在 C# 中一样。上面的代码片段中的 Operand
将代表 main 方法的单个参数。如果您不需要定义参数的名称 (请注意,在 Method()
调用中未定义参数名称),则可以完全跳过此操作,只需使用 g.Arg(0)
来访问参数。
4. 编写方法的正文
g.If(args.ArrayLength() > 0);
{
g.WriteLine("Hello " + args[0] + "!");
}
g.Else();
{
g.WriteLine("Hello World!");
}
g.End();
与 C# 版本进行比较:
if (args.Length > 0)
{
Console.WriteLine("Hello " + args[0] + "!");
}
else
{
Console.WriteLine("Hello World!");
}
正如您所见,代码非常相似。有几点需要注意 - g.WriteLine(...)
调用是一个特例,它们只是更长调用的快捷方式:
g.Invoke(typeof(Console), "WriteLine", ...)
您可以使用它来调用任何类型/实例上的任何方法。此外,您可以看到参数的长度是通过调用 args
操作数的 ArrayLength()
来获取的。这类似于调用 args.Property("Length")
,尽管它的性能稍好一些,因为它被翻译成 ldlen
操作码,而不是访问数组对象的属性。
5. 使用生成的代码
现在我们已经创建了一个简单的“hello world”应用程序,您可以选择保存并运行它:
// this will save the generated assembly
// to the file provided at construction time
ag.Save();
// execute the generated assembly
AppDomain.CurrentDomain.ExecuteAssembly(
"HelloWorld.exe", null, new string[] { "John" });
或者,您也可以通过调用 ag.Complete()
来完成程序集,并以任何您喜欢的方式使用生成的类型 (在定义类型时返回的 TypeGen
类隐式转换为 Type
)。
其他迷你示例
在“hello world”示例中,只展示了最简单的概念。当然,您可以使用 RunSharp
做更多的事情。以下是一些关于各种概念及其与 C# 关系的小示例。
1. 局部变量
C#
int x = 3;
long y = x + 15;
string s;
RunSharp
Operand x = g.Local(3);
Operand y = g.Local(typeof(long), x + 15);
Operand z = g.Local(typeof(string));
请注意,如果未指定变量的类型,它将自动采用初始化表达式的类型。此外,可以通过调用不带任何参数的 g.Local()
来创建无类型且未初始化的变量。此类变量将接收第一个赋值给它的表达式的类型。
请注意,可以编写如下代码:
Operand x = g.Local(3);
Operand y = x + 40;
但是,在这种情况下,y
不会是局部变量,而更像是表达式 x + 40
的“别名”,这意味着每次使用 y
时都会评估该表达式。
2. 语句
语句始终使用 CodeGen
类的成员生成。大多数基于 C# 关键字的语句在 RunSharp
中都有等效的方法 (例如,Break()
对应 break
,Throw()
对应 throw
,Return()
对应 return
等)。除此之外,只支持赋值、方法调用和增量/减量语句。
2.1 赋值、增量/减量
C#
a = 3;
b = a + 5;
c += 4;
b++;
RunSharp
g.Assign(a, 3);
g.Assign(b, a + 5);
g.AssignAdd(c, 4);
g.Increment(b);
请注意,赋值运算符不能在 C# 中重载。这就是我们需要一个专用方法来执行赋值的原因。此外,对于每个复合赋值运算符都有一个方法。
2.2. 方法调用
C#
// instance method
o.SomeMethod(arg1, arg2);
// static method of a system type
Console.WriteLine(arg1, arg2);
// static method of a custom type
MyType.MyMethod(arg1, arg2);
// delegate
d(arg1, arg2);
RunSharp
// instance method
g.Invoke(o, "SomeMethod", arg1, arg2);
// static method of a system type
g.Invoke(typeof(Console), "WriteLine", arg1, arg2);
// static method of a custom type
g.Invoke(MyType, "MyMethod", arg1, arg2);
// delegate
g.InvokeDelegate(d, arg1, arg2);
请注意,在调用生成类型的方法时,会省略 typeof()
- 这是因为 TypeGen
会隐式转换为 Type
。
3. 表达式
如前所述,Operand
类允许您像在 C# 中一样构建表达式。只有少数情况下直观的语法不可用。
3.1 前缀/后缀运算符
C#
x = a++;
y = --b;
RunSharp
g.Assign(x, a.PostIncrement());
g.Assign(y, b.PreDecrement());
3.2 条件运算符 (?:)
C#
x = a > 0 ? b : c;
RunSharp
g.Assign(x, (a > 0).Conditional(b, c));
您永远不应直接将 ?:
运算符与 Operand
实例作为第一个操作数一起使用 - 这将破坏 Operand
的内部状态,因为 true
和 false
运算符已重载,允许直观地使用 &&
和 ||
运算符。代替 ?:
,您可以使用 op.Conditional(ifTrue, ifFalse)
语法。如果您在 if
、while
、for
等中使用 Operand
作为条件,您也将破坏状态,但这通常不太可能,因为您而是使用 g.If()
等来生成代码。
3.3. 成员访问
C#
// instance member access
x = o.MyMethod(arg1, arg2);
y = o.MyProperty;
o.MyProperty = z;
f = o.myField;
// static member access
x = MyType.MyMethod(arg1, arg2);
y = Console.BackgroundColor;
RunSharp
// instance member access
g.Assign(x, o.Invoke("MyMethod", arg1, arg2));
g.Assign(y, o.Property("MyProperty"));
g.Assign(o.Property("MyProperty"), z);
g.Assign(f, o.Field("myField"));
// static member access
g.Assign(x, Static.Invoke(MyType, "MyMethod", arg1, arg2);
g.Assign(y, Static.Property(typeof(Console), "BackgroundColor");
3.4. 对象创建
C#
// instance creation
MyType x = new MyType(arg1, arg2);
// array creation
string x = new string[10, 20];
// initialized array creation
int[] x = { 1, 2, 3, 4 };
// delegate creation
MyDelegate x = new MyDelegate(this.MyMethod);
RunSharp
// instance creation
Operand x = g.Local(Exp.New(MyType, arg1, arg2));
// array creation
Operand x = g.Local(Exp.NewArray(typeof(string), 10, 20));
// initialized array creation
Operand x = g.Local(Exp.NewInitializedArray(typeof(int), 1, 2, 3, 4));
// delegate creation
Operand x = g.Local(Exp.NewDelegate(MyDelegate, g.This(), "MyMethod"));
联系我
如有任何问题、想法、建议等,请随时与我联系,例如通过 RunSharp
项目 此处。
祝您 Runtime
代码生成愉快!
历史
- 2007-10-18:原始文章
- 2009-08-07:更新了源代码下载