Why am I getting this exception when emitting classes that reference each other via value-type generics?

Viewed 1955

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

Which should produce MSIL equivalent to the following:

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

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

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

Interesting things to note:

  • 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.

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

Thanks.

1 Answers
Related