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:
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.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.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.TestGeneric<>
in my case as it is declared in another assembly - specifically, System.Data.Linq.EntityRef<>
declared in System.Data.Linq.dll.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.Any ideas on a) why this occuring, b) how I can fix this and/or c) how I can work around it?
Thanks.
I do not know exactly why this is occurring. I have a good guess.
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.
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.
Here is a rough implementation:
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;
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With