为什么我在发出通过值类型泛型相互引用的类时得到这个异常?

本文关键字:引用 异常 泛型 类型 为什么 | 更新日期: 2023-09-27 18:02:45

这段代码片段是我的类生成代码的简化摘录,它创建了两个类,它们在泛型类型中作为参数相互引用:

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直接不会产生此错误;因此,I 可以使用已定义但未创建的动态类型。
  • TestGeneric<>struct更改为class不会产生此错误;所以这个模式适用于大多数泛型,只是不是泛型值类型。
  • 我不能改变TestGeneric<>的声明,因为它是在另一个程序集中声明的-具体来说,System.Data.Linq.EntityRef<>在System.Data.Linq.dll中声明。
  • 我的循环引用是由两个表的外键相互引用引起的;因此需要特定的泛型类型和特定的模式。
  • 将循环引用更改为自引用edit成功。这最初失败是因为我在Program中将TestGeneric<>作为嵌套类型,因此它继承了internal的可见性。现在我已经在上面的代码示例中修复了这个问题,并且它确实工作了。
  • 手动编译生成的代码(如c#代码)也可以工作,所以这不是一个晦涩的编译器问题。

关于a)为什么会发生这种情况,b)我如何解决这个问题和/或c)我如何能够围绕它工作的任何想法?

谢谢。

为什么我在发出通过值类型泛型相互引用的类时得到这个异常?

我不知道为什么会发生这种情况。我猜得很准。

正如您所观察到的,创建泛型类与创建泛型结构的处理方式不同。当你创建类型"TypeOne"时,发射器需要创建泛型类型"TestGeneric",并且由于某种原因需要正确的type而不是TypeBuilder。当尝试确定新的泛型结构的大小时,可能会发生这种情况。我不确定。也许TypeBuilder不能计算出它的大小,所以需要创建'TypeTwo'类型。

当type2找不到时(因为它只作为TypeBuilder存在),AppDomain的TypeResolve事件将被触发。这给了你一个解决问题的机会。在处理typerresolve事件时,您可以创建类型'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;
            }
        }
    }
}