Emit 构造器
上一章我们讲到了使用 DynamicMethod
对象来自已编写 IL
指令,最终编译生成一个委托进行调用,它的局限性是很明显的。接下来本章将介绍如何动态构建程序集、类型、以及方法、属性等等。
1、程序集构造器
DynamicAssemblyBuilder
类用于构造一个指定名称的程序集,在此构造器内,再定义接口、类型和枚举等等,最终生成一个完整的程序集。DynamicAssemblyBuilder
类的属性和方法有以下这些:
- Defineype 方法
用于定义一个的类型,返回一个 DynamicTypeBuilder
实例。
- DefineInterface 方法
用于定义一个接口,返回一个 DynamicInterfaceBuilder
实例。
- DefineEnum 方法
用于定义一个枚举,返回一个 DynamicEnumBuilder
实例。
下面是一个程序集构造器的综合示例:
[TestMethod]
public void TestDefineAssembly()
{
var assemblyBuilder = new DynamicAssemblyBuilder("MyAssembly");
var interfaceBuilder = assemblyBuilder.DefineInterface("MyInterface");
var typeBuilder = assemblyBuilder.DefineType("MyClass");
var enumBuilder = assemblyBuilder.DefineEnum("MyEnum");
Assert.AreEqual("MyClass", typeBuilder.TypeBuilder.Name);
}
定义接口、类型和枚举时,可以使用 VisualDecoration
修饰其可见性,如公开、私有、内部和保护,使用 CallingDecoration
修饰访问性,如抽象、多态和静态。如下所示:
[TestMethod]
public void TestDefineAssemblyWithDecoration()
{
var assemblyBuilder = new DynamicAssemblyBuilder("MyAssembly");
var typeBuilder1 = assemblyBuilder.DefineType("MyPrivateClass", VisualDecoration.Private);
var typeBuilder2 = assemblyBuilder.DefineType("MyAbstractClass", calling: CallingDecoration.Abstract);
Assert.IsFalse(typeBuilder1.TypeBuilder.IsPublic);
Assert.IsTrue(typeBuilder2.TypeBuilder.IsAbstract);
}
.Net Framework 下,可以为 DynamicAssemblyBuilder
对象指定一个路径名,最后使用 Save 方法可将程序集保存到磁盘上。如下所示:
[TestMethod]
public void TestDefineAndSaveAssembly()
{
var assemblyBuilder = new DynamicAssemblyBuilder("MyAssembly", "c:\\MyAssembly.dll");
var interfaceBuilder = assemblyBuilder.DefineInterface("MyInterface");
var typeBuilder = assemblyBuilder.DefineType("MyClass");
var enumBuilder = assemblyBuilder.DefineEnum("MyEnum");
assemblyBuilder.Save();
}
2、类型构造器
DynamicTypeBuilder
类用来构造一个类型,它由 DynamicAssemblyBuilder
类的 DefineType 方法定义。DynamicTypeBuilder
类的属性和方法有以下这些:
- BaseType 属性
用于指定继承的基类。
- InterfaceTypes 属性
用于添加实现的接口。
- DefineConstructor 方法
用于定义一个构造函数,返回一个 DynamicConstructorBuilder
实例。
- DefineField 方法
用于定义一个字段,返回一个 DynamicFieldBuilder
实例。
- DefineProperty 方法
用于定义一个属性,返回一个 DynamicPropertyBuilder
实例。
- DefineMethod 方法
用于定义一个方法,返回一个 DynamicMethodBuilder
实例。
- DefineNestedType 方法
用于定义一个嵌套的类型,返回一个 DynamicTypeBuilder
实例。
- DefineNestedInterface 方法
用于定义一个嵌套的接口,返回一个 DynamicInterfaceBuilder
实例。
- DefineNestedEnum 方法
用于定义一个嵌套的枚举,返回一个 DynamicEnumBuilder
实例。
下面是一个类型构造器的综合示例:
public interface IMyInterface
{
void HelloWorld();
string Title { get; set; }
}
public class MyBaseClass
{
public virtual string GetName(string name)
{
return name;
}
}
[TestMethod]
public void TestDefineType()
{
var assemblyBuilder = new DynamicAssemblyBuilder("MyAssembly");
var typeBuilder = assemblyBuilder.DefineType("MyClass");
typeBuilder.BaseType = typeof(MyBaseClass);
typeBuilder.ImplementInterface(typeof(IMyInterface));
var methodBuilder = typeBuilder.DefineMethod("HelloWorld");
var propertyBuilder = typeBuilder.DefineProperty("Title", typeof(string)).DefineGetSetMethods();
methodBuilder = typeBuilder.DefineMethod("WriteName", typeof(string), new[] { typeof(string) });
var type = typeBuilder.CreateType();
Assert.IsTrue(typeof(IMyInterface).IsAssignableFrom(type));
}
3、接口构造器
DynamicInterfaceBuilder
类用来构造一个接口,它继承自 DynamicTypeBuilder
类,它由 DynamicAssemblyBuilder
类的 DefineInterface 方法定义。DynamicInterfaceBuilder
类的效效方法只有以下两个:
- DefineProperty 方法
用于定义一个属性,返回一个 DynamicPropertyBuilder
实例。
- DefineMethod 方法
用于定义一个方法,返回一个 DynamicMethodBuilder
实例。
4、构造函数构造器
DynamicConstructorBuilder
类用来构造一个构造函数,它由 DynamicAssemblyBuilder
类的 DefineConstructor 方法定义。DynamicInterfaceBuilder
类的属性和方法有以下这些:
- ParameterTypes 属性
返回构造函数的参数类型数组。
- DefineParameter 方法
用于定义构造器的一个参数的名称、默认值,注意该方法的调用次数不能超出 Parameters 数组的长度。
- AppendCode 方法
用于追加方法体的 IL
指令。
- OverwriteCode 方法
用于覆盖现有方法体的 IL
指令。
下面是一个程序集构造器的综合示例:
[TestMethod]
public void TestDefineConstructor()
{
var assemblyBuilder = new DynamicAssemblyBuilder("MyAssembly");
var typeBuilder = assemblyBuilder.DefineType("MyClass");
var constructorBuilder = typeBuilder.DefineConstructor(new Type[] { typeof(string), typeof(string) });
constructorBuilder.DefineParameter("str").DefineParameter("name");
constructorBuilder.OverwriteCode(e =>
e.ldarg_1.ldarg_2
.call(typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }))
.call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }))
.ret()
);
var type = typeBuilder.CreateType();
var obj = type.New("hello", "fireasy");
}
//------类型原型--------
public class MyClass()
{
public MyClass(string str, string name)
{
Console.WriteLine(string.Concat(str, name));
}
}
//----------------------
5、字段构造器
DynamicFieldBuilder
类用来定义一个字段,它由 DynamicTypeBuilder
类的 DefineField 方法定义,或由 DynamicEnumBuilder
类的 DefineLiteral 方法定义。DynamicFieldBuilder
类没有特别的方法。
6、属性构造器
DynamicPropertyBuilder
类用来构造一个属性,它由 DynamicTypeBuilder
或 DynamicInterfaceBuilder
类的 DefineProperty 方法定义。DynamicPropertyBuilder
类的属性和方法有以下这些:
- DefineGetSetMethods 方法
定义标准的 get / set 方法体,你还可以传入一个 DynamicFieldBuilder
对象作为其参数。
- DefineGetMethod / DefineSetMethod 方法
分别用于定义 get / set 方法体,返回一个 DynamicMethodBuilder
对象,可进行 IL
指令编写。
- DefineGetMethodByField / DefineSetMethodByField 方法
分别用于定义 get / set 方法体,返回一个 DynamicMethodBuilder
对象,可指定一个 DynamicFieldBuilder
对象作为其参数。
下面是一个属性构造器的综合示例:
[TestMethod]
public void TestDefineProperty()
{
var assemblyBuilder = new DynamicAssemblyBuilder("MyAssembly");
var typeBuilder = assemblyBuilder.DefineType("MyClass");
var fieldBuilder = typeBuilder.DefineField("_name", typeof(string));
var propertyBuilder = typeBuilder.DefineProperty("Name", typeof(string));
propertyBuilder.DefineGetMethod(fieldBuilder); // get only
Assert.IsTrue(propertyBuilder.PropertyBuilder.CanRead);
Assert.IsFalse(propertyBuilder.PropertyBuilder.CanWrite);
propertyBuilder = typeBuilder.DefineProperty("Address", typeof(string));
propertyBuilder.DefineGetSetMethods(); // get / set
Assert.IsTrue(propertyBuilder.PropertyBuilder.CanRead);
Assert.IsTrue(propertyBuilder.PropertyBuilder.CanWrite);
}
7、方法构造器
DynamicMethodBuilder
类用来构造一个属性,它由 DynamicTypeBuilder
或 DynamicInterfaceBuilder
类的 DefineMethod 方法定义,或由 DynamicPropertyBuilder
类的 DefineGetMethod / DefineSetMethod 方法定义。DynamicMethodBuilder
类的属性和方法有以下这些:
- ParameterTypes 属性
返回方法的参数类型数组。
- ReturnType 属性
获取或设置方法的返回类型。
- GenericArguments 属性
获取或设置方法的泛型参数。
- DefineParameter 方法
用于定义方法的一个参数的名称、默认值,注意该方法的调用次数不能超出 Parameters 数组的长度。
- AppendCode 方法
用于追加方法体的 IL
指令。
- OverwriteCode 方法
用于覆盖现有方法体的 IL
指令。
下面是一个方法构造器的综合示例:
[TestMethod]
public void TestDefineMethod()
{
var assemblyBuilder = new DynamicAssemblyBuilder("MyAssembly");
var typeBuilder = assemblyBuilder.DefineType("MyClass");
// 泛型方法 Helo<T1, T2>(string name, T1 any1, T2 any2)
var methodBuilder = typeBuilder.DefineMethod("Hello", parameterTypes: new Type[]
{
typeof(string), null, null
});
methodBuilder.GenericArguments = new string[] { string.Empty, "T1", "T2" };
methodBuilder.DefineParameter("name");
methodBuilder.DefineParameter("any1");
methodBuilder.DefineParameter("any2");
var paraCount = methodBuilder.ParameterTypes.Length;
methodBuilder.OverwriteCode(e =>
{
e.ldc_i4(paraCount)
.newarr(typeof(object))
.dup.ldc_i4_0.ldarg_1.stelem_ref //先把第一个string参数放到数组里
.For(1, paraCount, (e1, i) => //后面的两个,需要装箱
{
e1.dup.ldc_i4(i).ldarg(i + 1)
.box(methodBuilder.ParameterTypes[i])
.stelem_ref.end();
})
.call(typeof(string).GetMethod("Concat", new[] { typeof(object[]) }))
.call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }))
.ret();
});
var type = typeBuilder.CreateType();
var obj = type.New();
var method = type.GetMethod("Hello");
Assert.IsTrue(method.IsGenericMethod);
//构造一个泛型方法
method = method.MakeGenericMethod(typeof(int), typeof(decimal));
//调用方法
method.Invoke(obj, new object[] { "fireasy", 22, 45m });
}
8、枚举构造器
DynamicEnumBuilder
类用来构造一个枚举,它由 DynamicAssemblyBuilder
类的 DefineEnum 方法定义。DynamicEnumBuilder
类的方法目前只提供以下这一个:
- DefineLiteral 方法
定义一个枚举值。
9、自定义特性
所有的构造器都是继承自 DynamicBuilder
基类,它有一个可重写的 SetCustomAttribute 方法,用来添加自定义特性。如下所示:
[TestMethod]
public void TestSetCustomAttributes()
{
var assemblyBuilder = new DynamicAssemblyBuilder("MyAssembly");
var typeBuilder = assemblyBuilder.DefineType("MyClass");
typeBuilder.SetCustomAttribute<SerializableAttribute>();
//构造函数参数
typeBuilder.SetCustomAttribute<DescriptionAttribute>("测试类");
//或使用 lambda 表达式
typeBuilder.SetCustomAttribute(() => new DescriptionAttribute("测试类"));
}