为什么在发出通过值类型泛型引用彼此的类时会出现此异常?

Fac*_*Vir 16 c# generics reflection.emit

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

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>
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

哪个应该产生相当于以下的MSIL:

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

public class TypeTwo
{
    public Program.TestGeneric<TypeOne> One;
}
Run Code Online (Sandbox Code Playgroud)

但是反而抛出了这个异常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
Run Code Online (Sandbox Code Playgroud)

有趣的事情要注意:

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

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

谢谢.

Sco*_*ott 10

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

正如您所观察到的,创建泛型类的方式与创建泛型结构的方式不同.当您创建类型'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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)