2011-07-18 48 views
14

此代码段是我的类代码,这创建引用彼此作为参数以一般型两类简化提取物:为什么当发射通过值类型泛型相互引用的类时,我会得到此异常?

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 

利息注意事项:

  • 循环引用不需要引起异常;如果我没有在TypeTwo上定义字段One,则在TypeTwo之前创建TypeOne仍然失败,但在TypeOne成功之前创建TypeTwo。因此,该异常具体是由于在通用字段类型中使用尚未创建的类型作为参数而引起的;然而,因为我需要使用循环引用,所以我无法通过按特定顺序创建类型来避免这种情况。
  • 是的,我需要使用循环引用。
  • 删除包装TestGeneric<>类型并声明字段为TypeOne & TypeTwo直接不会产生此错误;因此我可以使用已定义但尚未创建的动态类型。
  • TestGeneric<>struct更改为class不会产生此错误;所以这种模式确实与大多数泛型一起工作,只是不是泛型值类型。
  • 在我的情况下,我无法更改TestGeneric<>的声明,因为它在另一个程序集中声明 - 具体地说,在System.Data.Linq.dll中声明System.Data.Linq.EntityRef<>
  • 我的循环引用是由表示两个表的外键彼此引用引起的;因此需要特定的通用类型和这种特定的模式。
  • 将循环引用更改为自引用编辑成功。原本这是因为我在程序中有TestGeneric<>作为嵌套类型,所以它继承了internal可见性。我已经在上面的代码示例中解决了这个问题,事实上它确实有效。
  • 手动编译生成的代码(如C#代码)也可以工作,所以它不是一个模糊的编译器问题。

任何想法a)为什么发生这种情况,b)我如何解决这个问题和/或c)我可以如何解决它?

谢谢。

回答

9

我不知道为什么会发生这种情况。我有一个很好的猜测。

正如您所观察到的,创建泛型类的方式与创建泛型结构不同。当你创建类型'TypeOne'时,发射器需要创建泛型类型'TestGeneric',由于某种原因需要正确的Type而不是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; 
      } 
     } 
    } 
} 
+0

有趣的;你实际上是在TypeResolver中创建typeTwo,这会导致它在创建的typeOne的中途创建,但是足够迟的时间以至于运行时处理到typeOne存在。这个代码的一个问题是,两种类型必须在创建之前完全定义;在这个例子中,我碰巧正在这样做,但必须确保在我的生产代码中执行该操作。无论如何,这解决了我的问题,所以谢谢! – FacticiusVir

+0

很高兴提供帮助。这是一个特别有趣/有趣的问题。事实上,涉及循环关系的类型必须准备在同一时间点创建。谢谢你指出。 – Scott

相关问题