【问题标题】:C# output a defined class to a dynamic module using Reflection.EmitC# 使用 Reflection.Emit 将定义的类输出到动态模块
【发布时间】:2011-12-07 15:22:35
【问题描述】:

微软在这里展示了如何创建动态类:

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

这定义了一个自定义对象,它们在其中定义了一个构造函数和一个方法。我定义了一个类,有没有办法发出我已经编写的类,而不是像示例所示那样尝试编写它?

感谢 FacticiusVir,它几乎完成了。然而,它似乎并不完全存在,'Countries.USA 不受该语言的支持'

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

【问题讨论】:

  • 您的Countries 对象,如所写,无法编译。您是否想要一个包含一组命名字段的 Country 类,每个字段都是 ReferencedObject?如果您可以在 C# 中给出您想要的可编译示例,那么为它编写 Reflection.Emit 代码会更容易。
  • 另外,您有 ReferenceType 和 Reference 的定义吗?
  • 您给出的示例不会产生您所描述的错误 - 具体是什么给您“Countries.USA is not supported by the language”?
  • 我添加了对已编译 dll 的引用,然后尝试使用 120 == countries.USA。我可能已经对其进行了排序(有点),我使用 ReferenceObject 类定义创建了一个 C# .dll 项目,将其复制到我的 Assembly 目录,然后在 DynamicEnum 项目(构建动态 dll 的项目)中引用了该 .dll。
  • 我一直在寻找一种方法将代码中的类添加到动态 dll 中,但无济于事......但也许我正在尝试做一些意想不到的事情。

标签: c# .net reflection dll dynamic


【解决方案1】:

好的,我假设 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源数据库更改。如果这是您正在寻找的,我建议您查看专用的代码生成工具,例如 Visual Studio 中内置的 T4。

您似乎在上面选择的选项是直接访问您生成的动态程序集,而它仍然保存在内存中。为此,您必须将程序集标记为可运行和可保存:

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

【讨论】:

  • 我认为这正是我所需要的。您在 Reference 和 ReferenceType 上是正确的(重要的是)。
  • 我认为国家只需要一个构造函数。
  • 我非常接近你给我的代码,我需要的 99%。但是它仍然不允许我指定 Country.USA,错误:“未按语言指定”我正在编辑问题以反映完整的代码。
  • 当我查看 ObjectBrowser 时,它会显示每个字段,但我无法从中获取值。
  • @FacticiousVir 我对 SO 有点陌生,所以我希望你能回来看看这个,因为你似乎知道发生了什么。
猜你喜欢
  • 1970-01-01
  • 2011-02-11
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 2021-12-23
  • 2016-09-06
  • 2018-06-15
  • 1970-01-01
相关资源
最近更新 更多