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 类用来构造一个属性,它由 DynamicTypeBuilderDynamicInterfaceBuilder 类的 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 类用来构造一个属性,它由 DynamicTypeBuilderDynamicInterfaceBuilder 类的 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("测试类")); 
}