Почему я получаю это исключение при испускании классов, которые ссылаются друг на друга через обобщенные типы значений? - PullRequest
16 голосов
/ 18 июля 2011

Этот фрагмент кода является упрощенным фрагментом моего кода генерации классов, в котором создаются два класса, которые ссылаются друг на друга как аргументы в универсальном типе:

namespace Sandbox
{
    using System;
    using System.Reflection;
    using System.Reflection.Emit;

    internal class Program
    {
        private static void Main(string[] args)
        {
            var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.Run);
            var module = assembly.DefineDynamicModule("Test");

            var typeOne = module.DefineType("TypeOne", TypeAttributes.Public);
            var typeTwo = module.DefineType("TypeTwo", TypeAttributes.Public);

            typeOne.DefineField("Two", typeof(TestGeneric<>).MakeGenericType(typeTwo), FieldAttributes.Public);
            typeTwo.DefineField("One", typeof(TestGeneric<>).MakeGenericType(typeOne), FieldAttributes.Public);

            typeOne.CreateType();
            typeTwo.CreateType();

            Console.WriteLine("Done");
            Console.ReadLine();
        }
    }

    public struct TestGeneric<T>
    {
    }
}

, который должен выдавать MSIL, эквивалентный следующему:

public class TypeOne
{
    public Program.TestGeneric<TypeTwo> Two;
}

public class TypeTwo
{
    public Program.TestGeneric<TypeOne> One;
}

Но вместо этого выбрасывает это исключение в строке typeOne.CreateType():

System.TypeLoadException was unhandled
  Message=Could not load type 'TypeTwo' from assembly 'Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
  Source=mscorlib
  TypeName=TypeTwo
  StackTrace:
       at System.Reflection.Emit.TypeBuilder.TermCreateClass(RuntimeModule module, Int32 tk, ObjectHandleOnStack type)
       at System.Reflection.Emit.TypeBuilder.CreateTypeNoLock()
       at System.Reflection.Emit.TypeBuilder.CreateType()
       at Sandbox.Program.Main(String[] args) in C:\Users\aca1\Code\Sandbox\Program.cs:line 20

Интересно отметить:

  • Циклическая ссылка нетребуется вызвать исключение;если я не определяю поле One для TypeTwo, создание TypeOne до TypeTwo все еще не выполняется, но создание TypeTwo до TypeOne завершается успешно.Следовательно, исключение специально вызывается использованием типа, который еще не был создан в качестве аргумента в универсальном типе поля;однако, поскольку мне нужно использовать циклическую ссылку, я не могу избежать этой ситуации, создавая типы в определенном порядке.
  • Да, мне do необходимо использовать циклическую ссылку.
  • Удаление обертки типа TestGeneric<> и объявление полей как TypeOne & TypeTwo напрямую не приводит к этой ошибке;таким образом, я могу использовать динамические типы, которые были определены, но не созданы.
  • Изменение TestGeneric<> с struct на class не приводит к этой ошибке;так что этот шаблон работает с большинством обобщенных типов, но не с универсальными типами значений.
  • Я не могу изменить объявление TestGeneric<> в моем случае, так как оно объявлено в другой сборке, в частности, System.Data.Linq.EntityRef<> объявлено в System.Data.Linq.dll.
  • Моя круговая ссылка вызвана представлением двух таблиц с ссылками на внешние ключи друг на друга;отсюда необходимость в этом конкретном родовом типе и этом конкретном шаблоне.
  • Изменение циклической ссылки на собственную ссылку edit завершается успешно.Первоначально это не удалось, потому что у меня был TestGeneric<> как вложенный тип в Программе, поэтому он унаследовал видимость internal.Я исправил это сейчас в приведенном выше примере кода, и он действительно работает.
  • Компиляция сгенерированного кода вручную (как код C #) также работает, поэтому это не проблема неясного компилятора.

Есть идеи: а) почему это происходит, б) как я могу это исправить и / или в) как я могу обойти это?

спасибо.

1 Ответ

10 голосов
/ 19 июля 2011

Я не знаю точно, почему это происходит.У меня есть хорошее предположение.

Как вы заметили, создание универсального класса трактуется иначе, чем создание универсальной структуры.Когда вы создаете тип «TypeOne», эмитент должен создать универсальный тип «TestGeneric», и по какой-то причине необходим правильный тип, а не TypeBuilder.Возможно, это происходит при попытке определить размер новой общей структуры?Я не уверен.Возможно, TypeBuilder не может определить его размер, поэтому необходим созданный тип TypeTwo.

Когда TypeTwo не может быть найден (поскольку он существует только как TypeBuilder), событие AppDomain TypeResolve будет вызвано.Это дает вам возможность решить проблему.При обработке события TypeResolve вы можете создать тип TypeTwo и решить проблему.

Вот примерная реализация:

namespace Sandbox
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Reflection.Emit;

    internal class Program
    {
        private static void Main(string[] args)
        {
            var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.Run);
            var module = assembly.DefineDynamicModule("Test");

            var typeOne = module.DefineType("TypeOne", TypeAttributes.Public);
            var typeTwo = module.DefineType("TypeTwo", TypeAttributes.Public);

            typeOne.DefineField("Two", typeof(TestGeneric<>).MakeGenericType(typeTwo), FieldAttributes.Public);
            typeTwo.DefineField("One", typeof(TestGeneric<>).MakeGenericType(typeOne), FieldAttributes.Public);

            TypeConflictResolver resolver = new TypeConflictResolver();
            resolver.AddTypeBuilder(typeTwo);
            resolver.Bind(AppDomain.CurrentDomain);

            typeOne.CreateType();
            typeTwo.CreateType();

            resolver.Release();

            Console.WriteLine("Done");
            Console.ReadLine();
        }
    }

    public struct TestGeneric<T>
    {
    }

    internal class TypeConflictResolver
    {
        private AppDomain _domain;
        private Dictionary<string, TypeBuilder> _builders = new Dictionary<string, TypeBuilder>();

        public void Bind(AppDomain domain)
        {
            domain.TypeResolve += Domain_TypeResolve;
        }

        public void Release()
        {
            if (_domain != null)
            {
                _domain.TypeResolve -= Domain_TypeResolve;
                _domain = null;
            }
        }

        public void AddTypeBuilder(TypeBuilder builder)
        {
            _builders.Add(builder.Name, builder);
        }

        Assembly Domain_TypeResolve(object sender, ResolveEventArgs args)
        {
            if (_builders.ContainsKey(args.Name))
            {
                return _builders[args.Name].CreateType().Assembly;
            }
            else
            {
                return null;
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...