当发出通过值类型泛型相互引用的类时,为什么会出现此异常? [英] Why am I getting this exception when emitting classes that reference each other via value-type generics?

查看:69
本文介绍了当发出通过值类型泛型相互引用的类时,为什么会出现此异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

This code snippet is a simplified extract of my class-generation code, which creates two classes that reference each other as arguments in a generic type:

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:

Which should produce MSIL equivalent to the following:

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

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

但是在 typeOne.CreateType()行上抛出此异常:

But instead throws this exception on the line 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.EntityRef<> .
  • 我的循环引用是通过将两个表相互表示为外键引用而引起的;因此需要特定的通用类型和特定的模式.
  • 将循环引用更改为自引用编辑成功.最初失败是因为我在程序中将 TestGeneric<> 作为嵌套类型,所以它继承了 internal 可见性.我已经在上面的代码示例中对此进行了修复,并且实际上可以正常工作.
  • 也可以手动编译生成的代码(作为C#代码),因此这不是一个晦涩的编译器问题.
  • The circular reference isn't required to cause the exception; if I don't define field One on TypeTwo, creating TypeOne before TypeTwo still fails, but creating TypeTwo before TypeOne succeeds. Therefore, the exception is specifically caused by using a type that has not yet been created as an argument in a generic field type; however, because I need to use a circular reference, I cannot avoid this situation by creating the types in a specific order.
  • Yes, I do need to use a circular reference.
  • Removing the wrapper TestGeneric<> type and declaring the fields as TypeOne & TypeTwo directly does not produce this error; thus I can use dynamic types that have been defined but not created.
  • Changing TestGeneric<> from a struct to a class does not produce this error; so this pattern does work with most generics, just not generic value types.
  • I can't change the declaration of TestGeneric<> in my case as it is declared in another assembly - specifically, System.Data.Linq.EntityRef<> declared in System.Data.Linq.dll.
  • My circular reference is caused by representing two tables with foreign key references to each other; hence the need for that specific generic type and this specific pattern.
  • Changing the circular reference to a self-reference edit succeeds. This failed originally because I had TestGeneric<> as a nested type in Program, so it inherited the internal visibility. I've fixed this now in the code sample above, and it does in fact work.
  • Compiling the generated code manually (as C# code) also works, so it's not an obscure compiler issue.

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

Any ideas on a) why this occuring, b) how I can fix this and/or c) how I can work around it?

谢谢.

推荐答案

我不知道为什么会这样.我有一个很好的猜测.

I do not know exactly why this is occurring. I have a good guess.

如您所见,创建泛型类与创建泛型结构的方式有所不同.当创建类型"TypeOne"时,发射器需要创建通用类型"TestGeneric",由于某种原因,需要正确的类型而不是TypeBuilder.尝试确定新泛型结构的大小时可能会发生这种情况?我不确定.也许TypeBuilder无法确定其大小,因此需要创建的"TypeTwo"类型.

As you have observed, creating a generic class is treated differently than creating a generic struct. When you create the type 'TypeOne' the emitter needs to create the generic type 'TestGeneric' and for some reason the proper Type is needed rather than the TypeBuilder. Perhaps this occurs when trying to determine the size of the new generic struct? I'm not sure. Maybe the TypeBuilder can't figure out its size so the created 'TypeTwo' Type is needed.

当无法找到TypeTwo时(因为它仅作为TypeBuilder存在),将触发AppDomain的TypeResolve事件.这使您有机会解决此问题.在处理TypeResolve事件时,您可以创建类型'TypeTwo'并解决问题.

When TypeTwo cannot be found (because it only exists as a TypeBuilder) the AppDomain's TypeResolve event will be triggered. This gives you a chance to fix the problem. While handling the TypeResolve event you can create the type 'TypeTwo' and solve the problem.

这是一个粗略的实现:

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

这篇关于当发出通过值类型泛型相互引用的类时,为什么会出现此异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆