C # выводит определенный класс в динамический модуль, используя Reflection.Emit - PullRequest
1 голос
/ 14 октября 2011

Microsoft показывает, как создать динамический класс здесь:

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

Это определяет пользовательский объект, где они определяют конструктор и метод. У меня есть определенный класс, есть ли способ создать класс, который я уже написал, вместо того, чтобы пытаться написать его, как показано в примере?

Спасибо FacticiusVir, это почти завершено. Однако, похоже, что это не совсем так: «Страны. США не поддерживаются языком»

Полный код, включая ответ FacticiusVir:

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 Ответ

2 голосов
/ 14 октября 2011

Хорошо, я предположил, что Reference & ReferenceType выглядит примерно так:

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

и класс, который вы пытаетесь сгенерировать, будет выглядеть примерно так:

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

Что вам нужно сделать, так это создать набор полей (я сделал их статическими и доступными только для чтения, что является хорошей практикой, если вы пытаетесь имитировать перечисления), а затем заполнить их из статического конструктора, например:

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

---- Редактировать ----

Чтобы получить доступ к значениям полей в сгенерированной DLL, у вас есть несколько вариантов. Первый - запустить этот код, взять копию файла «Dynamic Enums.dll», который он генерирует, и сослаться на него непосредственно из любого другого проекта, содержащего ваш код времени выполнения; у вас есть проект, который выполняется во время сборки для создания DLL (как указано выше) и второй, отдельный проект, который ссылается на DLL и выполняет работу вашего приложения во время выполнения. Преимущество этого состоит в том, что вы можете ссылаться на сгенерированные классы непосредственно в коде (например, SomeMethod(Countries.USA) или if(someVariable == Countries.CAN)), тогда как недостатком является то, что вы должны либо добавить приведенный выше код в процесс сборки, либо не забывать обновлять свои библиотеки DLL всякий раз, когда исходная база данных изменяется. Если это то, что вам нужно, я бы порекомендовал воспользоваться специальными инструментами для генерации кода, такими как T4, который встроен в Visual Studio.

Опция, которую вы, похоже, ищите выше, - это прямой доступ к динамической сборке, сгенерированной вами, пока она все еще находится в памяти. Для этого необходимо пометить сборку как работоспособную, так и сохраняемую:

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

По правде говоря, вы можете просто пометить его как AssemblyBuilderAccess.Run, но я предполагаю, что вы все еще хотите сохранить вывод.

Затем вы можете использовать метод FieldInfo.GetValue (object obj) для получения статического значения:

    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();
    }
...