2011-07-18 12 views
14

Ten fragment kodu jest uproszczony wyciąg z mojego kodu klasy generacji, która tworzy dwie klasy, które odwołują się do siebie jako argumenty w rodzaju ogólnego:Dlaczego otrzymuję ten wyjątek podczas emitowania klas, które odwołują się do siebie za pośrednictwem generycznych typów wartości?

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> 
    { 
    } 
} 

Które powinny produkować równowartość MSIL do następujących:

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

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

Ale zamiast rzuca ten wyjątek na linii 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 

turystyczne Uwagi:

  • Odwołanie nie jest wymagane, aby spowodować wyjątek; jeśli nie zdefiniuję pola One na TypeTwo, tworzenie TypeOne przed TypeTwo nadal się nie powiedzie, ale utworzenie TypeTwo przed TypeOne powiedzie się. Dlatego wyjątek jest w szczególności spowodowany użyciem typu, który nie został jeszcze utworzony jako argument w typowym polu; jednak, ponieważ muszę użyć odwołania cyklicznego, nie mogę uniknąć tej sytuacji, tworząc typy w określonej kolejności.
  • Tak, I do trzeba użyć okrągłego odniesienia.
  • Usuwanie typu opakowania TestGeneric<> i zadeklarowanie pól jako TypeOne & TypeTwo bezpośrednio nie powoduje wystąpienia tego błędu; w ten sposób mogę użyć dynamicznych typów, które zostały zdefiniowane, ale nie zostały utworzone.
  • Zmiana TestGeneric<> z struct na class nie powoduje wystąpienia tego błędu; więc ten wzorzec działa z większością typów generycznych, a nie generycznych.
  • Nie mogę zmienić deklaracji TestGeneric<> w moim przypadku, ponieważ jest zadeklarowany w innym zestawie - konkretnie, System.Data.Linq.EntityRef<> zadeklarowanym w System.Data.Linq.dll.
  • Moje odwołanie cykliczne jest spowodowane reprezentowaniem dwóch tabel z przypisanymi do nich kluczami obcymi; stąd potrzeba tego specyficznego typu generycznego i tego konkretnego wzorca.
  • Zmiana odniesienia cyklicznego do samodzielnego odniesienia edycja edycja kończy się sukcesem. To nie powiodło się pierwotnie, ponieważ miałem TestGeneric<> jako typ zagnieżdżony w Programie, więc odziedziczyło widoczność internal. Naprawiłem to teraz w powyższym przykładzie kodu, i faktycznie działa.
  • Kompilowanie wygenerowanego kodu ręcznie (jako kod C#) również działa, więc nie jest to problem niejasny dla kompilatora.

Wszelkie pomysły na a) dlaczego tak się dzieje, b) jak mogę to naprawić i/lub c) jak mogę obejść to?

Dzięki.

Odpowiedz

9

Nie wiem dokładnie, dlaczego tak się dzieje. Mam dobre domysły.

Jak zauważyłeś, tworzenie klasy ogólnej jest traktowane inaczej niż tworzenie ogólnej struktury.Po utworzeniu typu "TypeOne" emiter musi utworzyć typ ogólny "TestGeneric" iz jakiegoś powodu potrzebny jest właściwy Type, a nie TypeBuilder. Być może zdarza się to podczas próby określenia rozmiaru nowej ogólnej struktury? Nie jestem pewny. Może TypeBuilder nie może określić jego rozmiaru, więc potrzebny jest stworzony typ Type Type.

Gdy nie można znaleźć TypeTwo (ponieważ istnieje tylko jako TypeBuilder), zostanie wywołane zdarzenie AppDomain TypeResolve. Daje to szansę na rozwiązanie problemu. Podczas obsługi zdarzenia TypeResolve można utworzyć typ "TypeTwo" i rozwiązać problem.

Tutaj jest szorstka realizacja:

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; 
      } 
     } 
    } 
} 
+0

Ciekawe; tak naprawdę tworzysz typeTwo wewnątrz TypeResolver, co powoduje, że jest on tworzony w połowie tworzonego typeOne, ale wystarczająco późno, by środowisko wykonawcze działało na typeOne. Podstawą tego kodu jest to, że oba typy muszą być w pełni zdefiniowane, zanim można je utworzyć; Zdarza się, że robię to w tym przykładzie, ale muszę upewnić się, że wymuszam to w moim kodzie produkcyjnym. W każdym razie to rozwiązuje mój problem, więc dziękuję! – FacticiusVir

+0

Cieszę się, że mogę Ci pomóc. Był to szczególnie zabawny/interesujący problem, na który warto zwrócić uwagę. Rzeczywiście, typy, które są zaangażowane w cykliczną relację, muszą być gotowe do utworzenia w tym samym momencie. Dziękuję za wskazanie tego. – Scott

Powiązane problemy