IL Emit TypeBuilder и разрешение ссылок - PullRequest
3 голосов
/ 26 июля 2011

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


EDIT:

В итоге я пошел по маршруту (2), который определяет все соответствующие методы во время первого прохода, а затем испускает IL во втором проходе. Мне все еще было бы любопытно, можно ли получить экземпляры MethodBuilder (или экземпляры ConstructorBuilder) из TypeBuilder для компоновщиков, которые уже были определены в других местах.

Ответы [ 2 ]

0 голосов
/ 09 января 2017

Глядя на дизассемблированный код 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];
        }
    }
0 голосов
/ 26 июля 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...