C#输出定义的类使用Reflection.Emit的动态模块模块、定义、动态、Emit

2023-09-04 04:28:37 作者:别叫醒我的童年

微软展示了如何在这里营造出动态类:

的http://msdn.microsoft.com/en-us/library/system.reflection.emit.modulebuilder(v=vs.71).aspx

这定义了一个自定义对象,他们定义了一个构造函数和方法。我有一个类定义的,有没有办法发出我已经写了,而不是试图写它作为例子显示了类?

由于FacticiusVir,它几乎完成。然而,它似乎没有相当有,'Countries.USA不支持的语言'

全部code包括FacticiusVir的回答是:

 类DynamicEnums
{
    公共静态无效的主要()
    {
        AppDomain中域= AppDomain.CurrentDomain;

        的AssemblyName aName =新的AssemblyName(DynamicEnums);
        AssemblyBuilder AB = domain.DefineDynamicAssembly(aName,AssemblyBuilderAccess.Save);

        ModuleBuilder MB = ab.DefineDynamicModule(aName.Name,aName.Name +.DLL);

        ConstructorInfo referenceObjectConstructor = typeof运算(ReferenceObject).GetConstructor(新[] {typeof运算(INT)});

        名单<类型>类型=新的名单,其中,类型和GT;();

        的foreach(在GetTypes引用类型室温())
        {
            TypeBuilder TB = mb.DefineType(rt.Name,TypeAttributes.Public);

            ConstructorBuilder staticConstructorBuilder = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.Static,CallingConventions.Standard,Type.EmptyTypes);
            的ILGenerator staticConstructorILGenerator = staticConstructorBuilder.GetILGenerator();

            的foreach(在GetReferences参考R(rt.ID))
            {
                字符串名称;

                如果(rt.Name ==国家)
                    名称= r.Abbreviation.Trim();
                否则,如果(rt.Name ==PermanentFundDividends)
                    NAME =年+ r.Abbreviation.Trim();
                其他
                    名称= NameFix(r.Name);

                //创建一个公共的,静态的,只读域存储
                //命名ReferenceObject。
                FieldBuilder referenceObjectField = tb.DefineField(名称的typeof(ReferenceObject),FieldAttributes.Static | FieldAttributes.Public | FieldAttributes.InitOnly);

                //添加code静态构造函数来填充
                // ReferenceObject领域:

                //将ReferenceObject的ID值压入堆栈作为
                //字面4字节整数(Int32)已。
                staticConstructorILGenerator.Emit(欧普codes.Ldc_I4,r.ID);

                //创建一个参考一新ReferenceObject堆栈上
                //调用ReferenceObject(INT32 P值)参考
                //我们在前面创建。
                staticConstructorILGenerator.Emit(欧普codes.Newobj,referenceObjectConstructor);

                //存放ReferenceObject参照静态
                // ReferenceObject场。
                staticConstructorILGenerator.Emit(欧普codes.Stsfld,referenceObjectField);
            }
            staticConstructorILGenerator.Emit(欧普codes.Ret);

            types.Add(tb.CreateType());
        }

        尝试
        {
            ab.Save(aName.Name +.DLL);
        }
        赶上(例外)
        {
            Console.WriteLine(无法保存的.dll,文件必须已经被加载。);
        }

        的foreach(在类型T型)
        {
            的foreach(字段信息o在t.GetFields())
            {
                Console.WriteLine({0} {1} = {2},T,o.Name,后来); //不知道怎么弄的价值做这种方式
            }

            Console.WriteLine();
            //Console.ReadKey();
        }

        Console.WriteLine();
        Console.WriteLine(动态枚举内置成功。);

        //Console.ReadKey();
    }

    公共静态列表<引用类型> GetTypes()
    {
        名单<引用类型>引用类型=新的名单,其中,引用类型>();

        referenceTypes.Add(新引用类型{n = 1,名称=国家});
        返回引用类型;
    }

    公共静态列表<参考> GetReferences(INT TYPEID)
    {
        清单<参考>引用=新的List<参考>();

        references.Add(新基准{ID = 120,缩写=USA});

        返回参考;
    }

    公共结构引用类型
    {
        公众诠释ID;
        公共字符串名称;
    }

    公共结构参考
    {
        公众诠释ID;
        公众诠释typeid的;
        公共字符串缩写;
        公共字符串名称;
    }

    公共静态字符串NameFix(字符串名称)
    {
        //地带的所有非字母数字字符
        字符串R = Regex.Replace(姓名,@[^ \ W],);

        //枚举不能以数字开头
        如果(Regex.IsMatch(R,@^ \ D))
            R =N+ R;

        返回ř;
    }
}

公共类ReferenceObject
{
    私人只读int值;

    公共ReferenceObject(INT P值)
    {
        值= P值;
    }

    公共重写字符串的ToString()
    {
        返回value.ToString();
    }

    公共int值()
    {
        返回值;
    }

    公众诠释ID()
    {
        返回值;
    }

    #地区==操作符

    公共静态布尔运算符==(INT objLeft,Ref​​erenceObject objRight)
    {
        返回objLeft == objRight.value;
    }

    公共静态布尔运算符==(ReferenceObject objLeft,INT objRight)
    {
        返回objLeft.value == objRight;
    }

    公共静态布尔运算符==(字符串objLeft,Ref​​erenceObject objRight)
    {
        返回objLeft == objRight.value.ToString();
    }

    公共静态布尔运算符==(ReferenceObject objLeft,串objRight)
    {
        返回objLeft.value.ToString()== objRight;
    }

    #endregion

    #地区!=运算符

    公共静态布尔运算符!=(INT objLeft,Ref​​erenceObject objRight)
    {
        返回objLeft = objRight.value!;
    }

    公共静态布尔运算符!=(ReferenceObject objLeft,INT objRight)
    {
        返回objLeft.value = objRight!;
    }

    公共静态布尔运算符!=(字符串objLeft,Ref​​erenceObject objRight)
    {
        返回objLeft = objRight.value.ToString()!;
    }

    公共静态布尔运算符!=(ReferenceObject objLeft,串objRight)
    {
        返回objLeft.value.ToString()= objRight!;
    }

    #endregion

    公众覆盖布尔等于(obj对象)
    {
        如果((obj为ReferenceObject))
            返回值==((ReferenceObject)OBJ).value的;

        如果((obj为INT))
            返回值==(INT)目标文件;

        如果((obj是字符串))
            返回value.ToString()==(字符串)目标文件;

        返回false;
    }

    公众覆盖INT GetHash code()
    {
        返回值;
    }
}
 

解决方案

好吧,我认为参比,引用类型是这个样子:

 公共类引用类型
{
    公共字符串名称{;组; }

    公众诠释ID {获得;组; }
}

公共类参考
{
    公共字符串缩写{获得;组; }

    公众诠释ID {获得;组; }
}
 
.NET高级特性 Emit 类的定义

和你试图生成会看起来像这样的类:

 公共静态类国家
{
    公共静态只读ReferenceObject美国=新ReferenceObject(120);
    公共静态只读ReferenceObject CAN =新ReferenceObject(13);
    // ...
}
 

您需要做的是创建一组字段(我做这些静态和只读,也就是如果你想模仿枚举一个很好的做法),然后从一个静态构造函数,如填充它们:

 的AppDomain域= AppDomain.CurrentDomain;

的AssemblyName aName =新的AssemblyName(DynamicEnums);
AssemblyBuilder AB = domain.DefineDynamicAssembly(aName,AssemblyBuilderAccess.Save);

ModuleBuilder MB = ab.DefineDynamicModule(aName.Name,aName.Name +.DLL);

//存储一个句柄ReferenceObject(INT32 P值)
//构造函数。
ConstructorInfo referenceObjectConstructor = typeof运算(ReferenceObject).GetConstructor(新[] {typeof运算(INT)});

的foreach(在GetTypes引用类型室温())
{
    TypeBuilder TB = mb.DefineType(rt.Name,TypeAttributes.Public);

    //定义静态构造函数来填充ReferenceObject
    //领域。
    ConstructorBuilder staticConstructorBuilder = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.Static,CallingConventions.Standard,Type.EmptyTypes);
    的ILGenerator staticConstructorILGenerator = staticConstructorBuilder.GetILGenerator();

    的foreach(在GetReferences参考R(rt.ID))
    {
        字符串名称= r.Abbreviation.Trim();

        //创建一个公共的,静态的,只读域存储
        //命名ReferenceObject。
        FieldBuilder referenceObjectField = tb.DefineField(名称的typeof(ReferenceObject),FieldAttributes.Static | FieldAttributes.Public | FieldAttributes.InitOnly);

        //添加code静态构造函数来填充
        // ReferenceObject领域:

        //将ReferenceObject的ID值压入堆栈作为
        //字面4字节整数(Int32)已。
        staticConstructorILGenerator.Emit(欧普codes.Ldc_I4,r.ID);

        //创建一个参考一新ReferenceObject堆栈上
        //调用ReferenceObject(INT32 P值)参考
        //我们在前面创建。
        staticConstructorILGenerator.Emit(欧普codes.Newobj,referenceObjectConstructor);

        //存放ReferenceObject参照静态
        // ReferenceObject场。
        staticConstructorILGenerator.Emit(欧普codes.Stsfld,referenceObjectField);
    }

    //完成静态构造函数。
    staticConstructorILGenerator.Emit(欧普codes.Ret);

    tb.CreateType();
}

ab.Save(aName.Name +.DLL);
 

----编辑----

要访问的生成的DLL的字段的值,你有几种选择。首先是运行此code,采取动态Enums.dll的副本文件,它生成并参照直接从任何其他项目包括运行时code;如您有在构建时执行产生的DLL(如上),并引用该DLL并执行应用程序的运行时的工作第二,独立的项目一期工程。这样做的好处是,你可以参考生成的类直接在code(例如的someMethod(Countries.USA)如果(someVariable == Countries.CAN)),而缺点是您必须的工作code以上到您的构建过程中,还是要记得重新生成的DLL每当源数据库的变化。如果这是您要查找的内容,我建议你看专门的code代像T4,工具,它内置在Visual Studio中。

您似乎选项是想以上就是直接访问您生成的,而它在内存中依然保持动态装配。要做到这一点,你必须标记组件,可运行以及可保存的:

  AssemblyBuilder AB = domain.DefineDynamicAssembly(aName,AssemblyBuilderAccess.RunAndSave);
 

事实上,你可以只将其标记为 AssemblyBuilderAccess.Run ,但我假设你还是要保存的输出。

然后,您可以使用FieldInfo.GetValue(obj对象)方法得到的静态值:

 的foreach(在类型T型)
    {
        的foreach(字段信息o在t.GetFields())
        {
            //由于这是一个静态字段的类型'T'没有实例
            //需要得到字段值,所以只是通过空
            ReferenceObject值= o.GetValue(空)为ReferenceObject;
            Console.WriteLine({0} {1} = {2},T,o.Name,价值);
        }

        Console.WriteLine();
    }
 

Microsoft shows how to create a dynamic class here:

http://msdn.microsoft.com/en-us/library/system.reflection.emit.modulebuilder(v=vs.71).aspx

This defines a custom object, where they define a constructor and a method. I have a class defined, is there a way to emit the class I have already written instead of trying to write it as the example shows?

Thanks FacticiusVir, it's nearly complete. However it doesn't seem to be quite there, 'Countries.USA is not supported by the language'

Full code including FacticiusVir's answer:

class DynamicEnums
{
    public static void Main()
    {
        AppDomain domain = AppDomain.CurrentDomain;

        AssemblyName aName = new AssemblyName("DynamicEnums");
        AssemblyBuilder ab = domain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Save);

        ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");

        ConstructorInfo referenceObjectConstructor = typeof(ReferenceObject).GetConstructor(new[] { typeof(int) });

        List<Type> types = new List<Type>();

        foreach(ReferenceType rt in GetTypes())
        {
            TypeBuilder tb = mb.DefineType(rt.Name, TypeAttributes.Public);

            ConstructorBuilder staticConstructorBuilder = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, Type.EmptyTypes);
            ILGenerator staticConstructorILGenerator = staticConstructorBuilder.GetILGenerator();

            foreach (Reference r in GetReferences(rt.ID))
            {
                string name;

                if (rt.Name == "Countries")
                    name = r.Abbreviation.Trim();
                else if (rt.Name == "PermanentFundDividends")
                    name = "Year" + r.Abbreviation.Trim();
                else
                    name = NameFix(r.Name);

                // Create a public, static, readonly field to store the
                // named ReferenceObject.
                FieldBuilder referenceObjectField = tb.DefineField(name, typeof(ReferenceObject), FieldAttributes.Static | FieldAttributes.Public | FieldAttributes.InitOnly);

                // Add code to the static constructor to populate the
                // ReferenceObject field:

                // Load the ReferenceObject's ID value onto the stack as a
                // literal 4-byte integer (Int32).
                staticConstructorILGenerator.Emit(OpCodes.Ldc_I4, r.ID);

                // Create a reference to a new ReferenceObject on the stack
                // by calling the ReferenceObject(int32 pValue) reference
                // we created earlier.
                staticConstructorILGenerator.Emit(OpCodes.Newobj, referenceObjectConstructor);

                // Store the ReferenceObject reference to the static
                // ReferenceObject field.
                staticConstructorILGenerator.Emit(OpCodes.Stsfld, referenceObjectField);
            }
            staticConstructorILGenerator.Emit(OpCodes.Ret);

            types.Add(tb.CreateType());
        }

        try
        {
            ab.Save(aName.Name + ".dll");
        }
        catch (Exception)
        {
            Console.WriteLine("Could not save .dll, file must already be loaded.");
        }

        foreach (Type t in types)
        {
            foreach (FieldInfo o in t.GetFields())
            {
                Console.WriteLine("{0}.{1} = {2}", t, o.Name, "Later");  //Don't know how to get Value doing it this way
            }

            Console.WriteLine();
            //Console.ReadKey();
        }

        Console.WriteLine();
        Console.WriteLine("Dynamic Enums Built Successfully.");

        //Console.ReadKey();
    }

    public static List<ReferenceType> GetTypes()
    {
        List<ReferenceType> referenceTypes = new List<ReferenceType>();

        referenceTypes.Add(new ReferenceType { ID = 1, Name = "Countries" });
        return referenceTypes;
    }

    public static List<Reference> GetReferences(int typeID)
    {
        List<Reference> references = new List<Reference>();

        references.Add(new Reference { ID = 120, Abbreviation = "USA" });

        return references;
    }

    public struct ReferenceType
    {
        public int ID;
        public string Name;
    }

    public struct Reference
    {
        public int ID;
        public int TypeID;
        public string Abbreviation;
        public string Name;
    }

    public static string NameFix(string name)
    {
        //Strip all non alphanumeric characters
        string r = Regex.Replace(name, @"[^\w]", "");

        //Enums cannot begin with a number
        if (Regex.IsMatch(r, @"^\d"))
            r = "N" + r;

        return r;
    }
}

public class ReferenceObject
{
    private readonly int value;

    public ReferenceObject(int pValue)
    {
        value = pValue;
    }

    public override string ToString()
    {
        return value.ToString();
    }

    public int Value()
    {
        return value;
    }

    public int ID()
    {
        return value;
    }

    #region == Operator

    public static bool operator ==(int objLeft, ReferenceObject objRight)
    {
        return objLeft == objRight.value;
    }

    public static bool operator ==(ReferenceObject objLeft, int objRight)
    {
        return objLeft.value == objRight;
    }

    public static bool operator ==(string objLeft, ReferenceObject objRight)
    {
        return objLeft == objRight.value.ToString();
    }

    public static bool operator ==(ReferenceObject objLeft, string objRight)
    {
        return objLeft.value.ToString() == objRight;
    }

    #endregion

    #region != Operator

    public static bool operator !=(int objLeft, ReferenceObject objRight)
    {
        return objLeft != objRight.value;
    }

    public static bool operator !=(ReferenceObject objLeft, int objRight)
    {
        return objLeft.value != objRight;
    }

    public static bool operator !=(string objLeft, ReferenceObject objRight)
    {
        return objLeft != objRight.value.ToString();
    }

    public static bool operator !=(ReferenceObject objLeft, string objRight)
    {
        return objLeft.value.ToString() != objRight;
    }

    #endregion

    public override bool Equals(object obj)
    {
        if ((obj is ReferenceObject))
            return value == ((ReferenceObject)obj).value;

        if ((obj is int))
            return value == (int)obj;

        if ((obj is string))
            return value.ToString() == (string)obj;

        return false;
    }

    public override int GetHashCode()
    {
        return value;
    }
}

解决方案

Alright, I've assumed that Reference & ReferenceType look something like this:

public class ReferenceType
{
    public string Name { get; set; }

    public int ID { get; set; }
}

public class Reference
{
    public string Abbreviation { get; set; }

    public int ID { get; set; }
}

and that the class you're trying to generate will looking something like this:

public static class Countries
{
    public static readonly ReferenceObject USA = new ReferenceObject(120);
    public static readonly ReferenceObject CAN = new ReferenceObject(13);
    //...
}

What you need to do is created a set of fields (I've made these static and readonly, which is a good practice if you're trying to imitate enumerations) and then populate them from a static constructor, e.g.:

AppDomain domain = AppDomain.CurrentDomain;

AssemblyName aName = new AssemblyName("DynamicEnums");
AssemblyBuilder ab = domain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Save);

ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");

// Store a handle to the ReferenceObject(int32 pValue)
// constructor.
ConstructorInfo referenceObjectConstructor = typeof(ReferenceObject).GetConstructor(new[] { typeof(int) });

foreach (ReferenceType rt in GetTypes())
{
    TypeBuilder tb = mb.DefineType(rt.Name, TypeAttributes.Public);

    // Define a static constructor to populate the ReferenceObject
    // fields.
    ConstructorBuilder staticConstructorBuilder = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, Type.EmptyTypes);
    ILGenerator staticConstructorILGenerator = staticConstructorBuilder.GetILGenerator();

    foreach (Reference r in GetReferences(rt.ID))
    {
        string name = r.Abbreviation.Trim();

        // Create a public, static, readonly field to store the
        // named ReferenceObject.
        FieldBuilder referenceObjectField = tb.DefineField(name, typeof(ReferenceObject), FieldAttributes.Static | FieldAttributes.Public | FieldAttributes.InitOnly);

        // Add code to the static constructor to populate the
        // ReferenceObject field:

        // Load the ReferenceObject's ID value onto the stack as a
        // literal 4-byte integer (Int32).
        staticConstructorILGenerator.Emit(OpCodes.Ldc_I4, r.ID);

        // Create a reference to a new ReferenceObject on the stack
        // by calling the ReferenceObject(int32 pValue) reference
        // we created earlier.
        staticConstructorILGenerator.Emit(OpCodes.Newobj, referenceObjectConstructor);

        // Store the ReferenceObject reference to the static
        // ReferenceObject field.
        staticConstructorILGenerator.Emit(OpCodes.Stsfld, referenceObjectField);
    }

    // Finish the static constructor.
    staticConstructorILGenerator.Emit(OpCodes.Ret);

    tb.CreateType();
}

ab.Save(aName.Name + ".dll");

---- Edit ----

To access the values of the fields in the generated DLL, you've got a few options. The first is to run this code, take a copy of the "Dynamic Enums.dll" file it generates and reference that directly from whatever other project includes your runtime code; i.e. you have a project that executes at build time to produce the DLL (as above) and a second, separate project that references the DLL and does the run-time work of your application. The advantage of this is that you can refer to the generated classes directly in code (e.g. SomeMethod(Countries.USA) or if(someVariable == Countries.CAN)), while the downside is you must either work the code above into your build process, or remember to regenerate your DLLs whenever the source database changes. If this is what you're looking for, I'd recommend looking at dedicated code-generation tools like T4, which is built into Visual Studio.

The option you seem to be going for above is to directly access the dynamic assembly that you generated while it is still held in memory. To do this, you must mark the assembly as runnable as well as savable:

AssemblyBuilder ab = domain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndSave);

In truth, you could just mark it as AssemblyBuilderAccess.Run but I'm assuming you still want to save the output.

You can then use the FieldInfo.GetValue(object obj) method to get the static value:

    foreach (Type t in types)
    {
        foreach (FieldInfo o in t.GetFields())
        {
            // As this is a static field no instance of type 't' is
            // required to get the field value, so just pass null
            ReferenceObject value = o.GetValue(null) as ReferenceObject;
            Console.WriteLine("{0}.{1} = {2}", t, o.Name, value);
        }

        Console.WriteLine();
    }