C# 为什么在发出通过值类型泛型相互引用的类时会出现这种异常?

C# 为什么在发出通过值类型泛型相互引用的类时会出现这种异常?,c#,generics,reflection.emit,C#,Generics,Reflection.emit,此代码段是我的类生成代码的简化摘录,它创建了两个类,它们作为泛型类型中的参数相互引用: namespace Sandbox { using System; using System.Reflection; using System.Reflection.Emit; internal class Program { private static void Main(string[] args) { v

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

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>
    {
    }
}
值得注意的有趣事情:

  • 循环引用不需要引起异常;如果我没有在
    TypeTwo
    上定义字段
    One
    ,在
    TypeTwo
    之前创建
    TypeOne
    仍然失败,但在
    TypeOne
    之前创建
    TypeTwo
    成功。因此,使用尚未创建的类型作为泛型字段类型中的参数会导致异常;但是,由于我需要使用循环引用,我无法通过按特定顺序创建类型来避免这种情况
  • 是的,我确实需要使用循环引用
  • 删除包装器
    TestGeneric
    type并直接将字段声明为
    TypeOne
    TypeTwo
    不会产生此错误;因此,我可以使用已定义但未创建的动态类型
  • TestGeneric
    struct
    更改为
    class
    不会产生此错误;因此,此模式适用于大多数泛型,而不是泛型值类型
  • 在我的例子中,我无法更改
    TestGeneric
    的声明,因为它是在另一个程序集中声明的-具体地说,
    System.Data.Linq.EntityRef
    是在System.Data.Linq.dll中声明的
  • 我的循环引用是由于用外键相互引用表示两个表引起的;因此需要特定的泛型类型和特定的模式
  • 将循环引用更改为自引用编辑成功。这一操作最初失败是因为我在程序中使用了
    TestGeneric
    作为嵌套类型,所以它继承了
    internal
    可见性。我已经在上面的代码示例中修复了这个问题,而且它确实有效
  • 手动编译生成的代码(就像C#代码一样)也可以工作,因此这不是一个模糊的编译器问题
关于a)为什么会发生这种情况,b)我如何解决这个问题和/或c)我如何解决它,有什么想法吗


谢谢。

我不知道发生这种情况的确切原因。我有一个很好的猜测

正如您所观察到的,创建泛型类与创建泛型结构的处理方式不同。当您创建类型“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;
            }
        }
    }
}
名称空间沙盒
{
使用制度;
使用System.Collections.Generic;
运用系统反思;
使用System.Reflection.Emit;
内部课程计划
{
私有静态void Main(字符串[]args)
{
var assembly=AppDomain.CurrentDomain.DefinedDynamicAssembly(新的AssemblyName(“测试”),AssemblyBuilderAccess.Run);
var模块=组件。定义动态模块(“测试”);
var typeOne=module.DefineType(“typeOne”,TypeAttributes.Public);
var typeTwo=module.DefineType(“typeTwo”,TypeAttributes.Public);
typeOne.DefineField(“两”,typeof(TestGeneric).MakeGenericType(typeTwo),FieldAttributes.Public);
typeTwo.DefineField(“一”,typeof(TestGeneric).MakeGenericType(typeOne),FieldAttributes.Public);
TypeConflictResolver解析器=新的TypeConflictResolver();
解析程序AddTypeBuilder(类型二);
解析程序绑定(AppDomain.CurrentDomain);
typeOne.CreateType();
typeTwo.CreateType();
解析程序Release();
控制台。写入线(“完成”);
Console.ReadLine();
}
}
公共结构TestGeneric
{
}
内部类TypeConflictResolver
{
私有AppDomain _域;
专用词典_builders=新词典();
公共无效绑定(AppDomain域)
{
domain.TypeResolve+=域_TypeResolve;
}
公开无效释放()
{
如果(_域!=null)
{
_domain.TypeResolve-=domain\u TypeResolve;
_域=空;
}
}
公共void AddTypeBuilder(类型生成器)
{
_builders.Add(builder.Name,builder);
}
程序集域\u类型解析(对象发送方,ResolveEventArgs args args)
{
if(_builders.ContainsKey(args.Name))
{
返回_builders[args.Name].CreateType().Assembly;
}
其他的
{
返回null;
}
}
}
}

有趣;实际上,您正在TypeResolver内部创建typeTwo,这会导致在创建typeOne的过程中创建它,但是创建的时间太晚了,以至于存在typeOne的运行时句柄。这段代码的一个问题是,在创建任何一种类型之前,必须完全定义这两种类型;在本例中,我恰好是这样做的,但必须确保在我的生产代码中强制执行这一点。无论如何,这解决了我的问题,谢谢!很乐意帮忙。这是一个特别有趣的问题。事实上,循环关系中涉及的类型必须在同一时间点准备好创建。谢谢你指出这一点。
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
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;
            }
        }
    }
}