C# output a defined class to a dynamic module usin

2019-06-07 05:21发布

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;
    }
}

1条回答
Rolldiameter
2楼-- · 2019-06-07 06:04

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();
    }
查看更多
登录 后发表回答