65.9K
CodeProject 正在变化。 阅读更多。
Home

RunSharp - Reflection.Emit 从未如此简单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (47投票s)

2007 年 10 月 18 日

MIT

5分钟阅读

viewsIcon

124402

downloadIcon

527

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");

此调用在后台简单地创建 AssemblyBuilderModuleBuilder

2. 定义您的第一个类

TypeGen MyClass = ag.Public.Class("MyClass");
{
  // we'll continue here
} 

正如您所见,这与您在 C# 中编写的内容非常相似,例如:

public class MyClass
{
  // we'll continue here
}

您还可以为类使用其他修饰符,例如 PrivateSealedAbstractNoBeforeFieldInit (后者允许在访问任何类成员之前或之后调用 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 变量的定义。OperandRunSharp 中的核心类之一,代表字面量、参数、局部变量、表达式等。它还重载了所有运算符,这些运算符可用于构建表达式,就像您在 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() 对应 breakThrow() 对应 throwReturn() 对应 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 的内部状态,因为 truefalse 运算符已重载,允许直观地使用 &&|| 运算符。代替 ?:,您可以使用 op.Conditional(ifTrue, ifFalse) 语法。如果您在 ifwhilefor 等中使用 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:更新了源代码下载
© . All rights reserved.