2011-07-18 17 views
14

Este fragmento de código es un extracto simplificada de mi código de clase generación, lo que crea dos clases que hacen referencia entre sí como argumentos en un tipo genérico:¿Por qué recibo esta excepción al emitir clases que se referencian entre sí a través de genéricos de tipo valor?

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

que debe producir MSIL equivalente a lo siguiente:

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

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

Pero en vez emite esta excepción en la línea 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 

interés Cosas a tener en cuenta:

  • La referencia circular no es necesaria para causar la excepción; si no defino el campo One en TypeTwo, crear TypeOne antes de que TypeTwo aún falle, pero la creación de TypeTwo antes de TypeOne se realiza correctamente. Por lo tanto, la excepción se debe específicamente al uso de un tipo que aún no se ha creado como un argumento en un tipo de campo genérico; sin embargo, debido a que necesito usar una referencia circular, no puedo evitar esta situación creando los tipos en un orden específico.
  • Sí, I do necesito usar una referencia circular.
  • Extracción de la envoltura TestGeneric<> tipo y declarando los campos como TypeOne & TypeTwo directamente no produce este error; por lo tanto, I puede utilizar tipos dinámicos que se han definido pero no creado.
  • Cambiando TestGeneric<> de struct a class no produce este error; por lo que este patrón funciona con con la mayoría de los genéricos, simplemente no con tipos de valores genéricos.
  • No puedo cambiar la declaración de TestGeneric<> en mi caso como se declara en otro ensamblado, específicamente, System.Data.Linq.EntityRef<> declarado en System.Data.Linq.dll.
  • Mi referencia circular se produce al representar dos tablas con referencias de clave externa entre sí; de ahí la necesidad de ese tipo genérico específico y este patrón específico.
  • Cambiando la referencia circular a una autorreferencia edit tiene éxito. Esto falló originalmente porque tenía TestGeneric<> como un tipo anidado en el Programa, por lo que heredó la visibilidad internal. He solucionado esto ahora en el ejemplo de código anterior, y de hecho funciona.
  • La compilación del código generado manualmente (como código C#) también funciona, por lo que no es un problema oscuro del compilador.

Alguna idea sobre a) por qué ocurre esto, b) cómo puedo solucionar esto y/o c) cómo puedo evitarlo?

Gracias.

Respuesta

9

No sé exactamente por qué ocurre esto. Tengo una buena suposición.

Como ha observado, la creación de una clase genérica se trata de manera diferente que la creación de una estructura genérica.Cuando crea el tipo 'TypeOne', el emisor necesita crear el tipo genérico 'TestGeneric' y, por alguna razón, se necesita el tipo apropiado en lugar de TypeBuilder. Tal vez esto ocurra al tratar de determinar el tamaño de la nueva estructura genérica? No estoy seguro. Tal vez el TypeBuilder no puede determinar su tamaño, por lo que se necesita el tipo creado 'TypeTwo'.

Cuando no se puede encontrar TypeTwo (porque solo existe como TypeBuilder) se activará el evento TypeResolve de AppDomain. Esto te da la oportunidad de solucionar el problema. Al manejar el evento TypeResolve, puede crear el tipo 'TypeTwo' y resolver el problema.

Aquí es una aplicación aproximada:

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

interesante; en realidad está creando typeTwo dentro de TypeResolver, lo que hace que se cree a mitad de camino a través de typeOne que se está creando, pero lo suficientemente tarde como para que el tiempo de ejecución maneje a typeOne existe. Un problema para este código es que ambos tipos deben estar completamente definidos antes de que cualquiera pueda ser creado; Estoy haciendo eso en este ejemplo, pero debo asegurarme de hacerlo cumplir en mi código de producción. En cualquier caso, esto resuelve mi problema, ¡así que gracias! – FacticiusVir

+0

Me alegra ayudar. Este fue un problema particularmente divertido/interesante de mirar. De hecho, los tipos que están involucrados en la relación circular deben estar listos para la creación en el mismo momento. Gracias por señalar eso. – Scott

Cuestiones relacionadas