IL Emit TypeBuilder и разрешение ссылок

Я испускаю несколько классов, некоторые из которых должны создавать своих сверстников в своих собственных конструкторах. Нет бесконечных рекурсивных зависимостей (поэтому, если 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 на втором проходе. Мне все еще было бы любопытно, возможно ли получить экземпляры MethodBuilder (или экземпляры ConstructorBuilder) из TypeBuilder для сборщиков, которые уже были определены в другом месте.


person Dan Bryant    schedule 26.07.2011    source источник


Ответы (2)


Я не эксперт по TypeBuilder, но у него есть метод .GetConstructor ( http://msdn.microsoft.com/en-us/library/cs01xzbk.aspx ) и .GetMethod ( http://msdn.microsoft.com/en-us/library/4s2kzbw8.aspx), который должен иметь возможность возвращать объявленный конструктор, если вы вызываете их для своего кэшированного fieldType...

person Yahia    schedule 26.07.2011
comment
GetConstructor возвращает ConstructorInfo, что не позволяет мне получить доступ к ILGenerator. Я считаю, что могу использовать GetConstructor, чтобы найти ссылку для вызова конструктора, но это означает, что мне нужно сначала определить его. - person Dan Bryant; 26.07.2011

Глядя на дизассемблированный код TypeBuilder, кажется, что это невозможно, если вам нужно более одного конструктора для каждого типа:

TypeBuilder.DefineConstructor просто вызывает DefineConstructorNoLock, который просто проверяет параметры и увеличивает значение поляstructorCount:

[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, но вы будете знать, что уже определили его.

Если вы действительно хотите получить конструкторBuilder и не хотите создавать слишком много перегрузок (например, 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];
        }
    }
person Regis Portalez    schedule 09.01.2017