IL 发出 TypeBuilder 并解析引用

发布于 2024-11-26 10:20:49 字数 1709 浏览 2 评论 0原文

我正在发出几个类,其中一些类需要在自己的构造函数中构造它们的同级。不存在无限递归依赖关系(因此,如果 A 构造 B,B 将不会构造 A;这对于嵌套引用也适用 [A 构造 B 构造 C 意味着 B 和 C 都不会构造 A])。我目前正在编写发出构造函数的代码,但遇到了一些问题。我不知道预先的依赖关系的顺序,所以似乎我有几个选择:

  1. 以某种方式按依赖关系对类进行排序,并按照依赖关系的顺序“构建”它们,因此更多依赖的类具有有效的要抓取的构造函数引用。
  2. 在第一遍中单独定义所有构造函数(无需实际发出方法的 IL),以便定义所有构造函数引用。
  3. 以某种方式缓存已定义的构造函数,以便如果尚未定义构造函数,我可以创建一个占位符 ConstructorBuilder 来获取引用,稍后在构造函数最终发出时将获取该引用。

我目前正在尝试选项(3),我想知道是否已经有一种方法可以从 TypeBuilder 中做到这一点。我的代码看起来像这样(在需要时获取构造函数引用):

            var fieldType = DefineType(udtField.Type); // This looks up a cached TypeBuilder or creates a placeholder, if needed
            var constructor = fieldType.GetConstructor(Type.EmptyTypes);
            if (constructor == null)
            {
                constructor =
                    fieldType.DefineConstructor(
                        MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                        CallingConventions.Standard, Type.EmptyTypes);
            }

我的 Build 方法当前像这样启动(我认为如果先前定义了构造函数,这将不起作用):

    private void BuildConstructor()
    {
        var method =
            DefinedType.DefineConstructor(
                MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                CallingConventions.Standard, Type.EmptyTypes);
        var il = method.GetILGenerator();

有什么方法可以查找先前定义的 ConstructorBuilder(如果已定义),而无需创建我自己的显式缓存?似乎 TypeBuilder 应该知道它,但我看不到任何明显的方法来从 TypeBuilder 文档中查找它。


编辑:

我最终沿着路线 (2) 走下去,它在第一次传递期间定义了所有相关方法,然后在第二次传递中发出 IL。我仍然很好奇是否可以从 TypeBuilder 获取已经在其他地方定义的构建器的 MethodBuilder 实例(或 ConstructorBuilder 实例)。

I am emitting several classes, some of which need to construct their peers in their own constructors. There are no infinite recursive dependencies (so if A constructs B, B won't construct A; this holds true for nested references as well [A constructs B constructs C means neither B nor C will construct A]). I am currently working on the code that emits the constructors and I have a bit of an issue. I don't know the order of dependencies up-front, so it seems that I have a few options:

  1. Somehow sort the classes by their dependencies and 'build' them in the order of their dependencies, so the more dependent classes have a valid constructor reference to grab.
  2. Define all of the constructors separately in a first pass (without actually emitting the IL for the methods), so that all of the constructor references are defined.
  3. Somehow cache the defined constructors so that if the constructor has not yet been defined, I can create a place-holder ConstructorBuilder to get the reference, which will then be grabbed later when the constructor is finally emitted.

I'm currently attempting option (3) and I was wondering if there is already a way to do this from TypeBuilder. I have code that looks like this (to grab a constructor reference when its needed):

            var fieldType = DefineType(udtField.Type); // This looks up a cached TypeBuilder or creates a placeholder, if needed
            var constructor = fieldType.GetConstructor(Type.EmptyTypes);
            if (constructor == null)
            {
                constructor =
                    fieldType.DefineConstructor(
                        MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                        CallingConventions.Standard, Type.EmptyTypes);
            }

And my Build method currently starts like this (I don't think this will work if the constructor was previously defined):

    private void BuildConstructor()
    {
        var method =
            DefinedType.DefineConstructor(
                MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                CallingConventions.Standard, Type.EmptyTypes);
        var il = method.GetILGenerator();

Is there some way that I can look up the ConstructorBuilder that was previously defined (if it has been), without having to create my own explicit cache? It seems like the TypeBuilder should know about it, but I can't see any obvious way to look it up from the TypeBuilder documentation.


EDIT:

I've ended up going down route (2), which defines all of the relevant methods during a first pass, then emits the IL afterward in a second pass. I'd still be curious if it's possible to get MethodBuilder instances (or ConstructorBuilder instances) from the TypeBuilder for builders that have already been defined elsewhere.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

云胡 2024-12-03 10:20:49

I am no expert on TypeBuilder but it has a method .GetConstructor ( http://msdn.microsoft.com/en-us/library/cs01xzbk.aspx ) and .GetMethod ( http://msdn.microsoft.com/en-us/library/4s2kzbw8.aspx ) which should be able to return the declared constructor if you call them on your cached fieldType...

沫雨熙 2024-12-03 10:20:49

通过查看 TypeBuilder 的反汇编代码,您似乎不可能希望每种类型有多个构造函数:

TypeBuilder.DefineConstructor 仅调用 DefineConstructorNoLock,后者仅检查参数和增量constructorCount 字段:

[SecurityCritical]
private ConstructorBuilder DefineConstructorNoLock(MethodAttributes attributes, CallingConventions callingConvention, Type[] parameterTypes, Type[][] requiredCustomModifiers, Type[][] optionalCustomModifiers)
{
    this.CheckContext(parameterTypes);
    this.CheckContext(requiredCustomModifiers);
    this.CheckContext(optionalCustomModifiers);
    this.ThrowIfCreated();
    string name;
    if ((attributes & MethodAttributes.Static) == MethodAttributes.PrivateScope)
    {
        name = ConstructorInfo.ConstructorName;
    }
    else
    {
        name = ConstructorInfo.TypeConstructorName;
    }
    attributes |= MethodAttributes.SpecialName;
    ConstructorBuilder result = new ConstructorBuilder(name, attributes, callingConvention, parameterTypes, requiredCustomModifiers, optionalCustomModifiers, this.m_module, this);
    this.m_constructorCount++;
    return result;
}

因此,如果您只想为每种类型定义一个构造函数,则可以检查此属性(使用反射,因为它是私有字段)并检查其值:

namespace ConsoleApplication7
{
    static class TypeBuilderExtension
    {
        public static int GetConstructorCount(this TypeBuilder t)
        {
            FieldInfo constCountField = typeof(TypeBuilder).GetField("m_constructorCount", BindingFlags.NonPublic | BindingFlags.Instance);
            return (int) constCountField.GetValue(t);
        }
    }

    class Program
    {  
        static void Main(string[] args)
        {
            AppDomain ad = AppDomain.CurrentDomain;
            AssemblyBuilder ab = ad.DefineDynamicAssembly(new AssemblyName("toto.dll"), AssemblyBuilderAccess.RunAndSave);
            ModuleBuilder mb = ab.DefineDynamicModule("toto.dll");
            TypeBuilder tb = mb.DefineType("mytype");
            
            Console.WriteLine("before constructor creation : " + tb.GetConstructorCount());

            ConstructorBuilder cb = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[0]);
            ILGenerator ilgen = cb.GetILGenerator();
            ilgen.Emit(OpCodes.Ret);
            Console.WriteLine("after constructor creation : " + tb.GetConstructorCount());

            tb.CreateType();
            ab.Save("toto.dll");
        }
    }
}

其输出:

构造函数创建之前:0

构造函数创建后:1

这不会为您提供实际的 ConstructorBuilder,但您会知道您已经定义了它。

如果您想实际获取 constructorBuilder,并且不想创建太多重载(例如 1),我会使用扩展方法选择选项 3:

    static class TypeBuilderExtension
    {
        private static Dictionary<TypeBuilder, ConstructorBuilder> _cache = new Dictionary<TypeBuilder, ConstructorBuilder>();

        public static ConstructorBuilder DefineMyConstructor(this TypeBuilder tb)
        {
            if (!_cache.ContainsKey(tb))
            {
                _cache.Add(tb, tb.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[0]));
            }

            return _cache[tb];
        }
    }

By looking at the disassembled code of TypeBuilder, it appears it's not possible it you want more than one constructor per type :

TypeBuilder.DefineConstructor just calls DefineConstructorNoLock which just checks parameters and increments the constructorCount field :

[SecurityCritical]
private ConstructorBuilder DefineConstructorNoLock(MethodAttributes attributes, CallingConventions callingConvention, Type[] parameterTypes, Type[][] requiredCustomModifiers, Type[][] optionalCustomModifiers)
{
    this.CheckContext(parameterTypes);
    this.CheckContext(requiredCustomModifiers);
    this.CheckContext(optionalCustomModifiers);
    this.ThrowIfCreated();
    string name;
    if ((attributes & MethodAttributes.Static) == MethodAttributes.PrivateScope)
    {
        name = ConstructorInfo.ConstructorName;
    }
    else
    {
        name = ConstructorInfo.TypeConstructorName;
    }
    attributes |= MethodAttributes.SpecialName;
    ConstructorBuilder result = new ConstructorBuilder(name, attributes, callingConvention, parameterTypes, requiredCustomModifiers, optionalCustomModifiers, this.m_module, this);
    this.m_constructorCount++;
    return result;
}

So if you just want to define one constructor per type, you can check this property (using reflection, since it's a private field) and check its value :

namespace ConsoleApplication7
{
    static class TypeBuilderExtension
    {
        public static int GetConstructorCount(this TypeBuilder t)
        {
            FieldInfo constCountField = typeof(TypeBuilder).GetField("m_constructorCount", BindingFlags.NonPublic | BindingFlags.Instance);
            return (int) constCountField.GetValue(t);
        }
    }

    class Program
    {  
        static void Main(string[] args)
        {
            AppDomain ad = AppDomain.CurrentDomain;
            AssemblyBuilder ab = ad.DefineDynamicAssembly(new AssemblyName("toto.dll"), AssemblyBuilderAccess.RunAndSave);
            ModuleBuilder mb = ab.DefineDynamicModule("toto.dll");
            TypeBuilder tb = mb.DefineType("mytype");
            
            Console.WriteLine("before constructor creation : " + tb.GetConstructorCount());

            ConstructorBuilder cb = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[0]);
            ILGenerator ilgen = cb.GetILGenerator();
            ilgen.Emit(OpCodes.Ret);
            Console.WriteLine("after constructor creation : " + tb.GetConstructorCount());

            tb.CreateType();
            ab.Save("toto.dll");
        }
    }
}

which outputs :

before constructor creation : 0

after constructor creation : 1

This won't give you the actual ConstructorBuilder, but you'll know you already defined it.

If you want to actually get the constructorBuilder, and you don't want to create too many overloads (1 for example), I'd go for your option 3 with an extension method:

    static class TypeBuilderExtension
    {
        private static Dictionary<TypeBuilder, ConstructorBuilder> _cache = new Dictionary<TypeBuilder, ConstructorBuilder>();

        public static ConstructorBuilder DefineMyConstructor(this TypeBuilder tb)
        {
            if (!_cache.ContainsKey(tb))
            {
                _cache.Add(tb, tb.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[0]));
            }

            return _cache[tb];
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文