I am emitting several classes, some of which need to construct their peers in their own constructors. There are no infinite recursive dependencies (so if A constructs B, B won't construct A; this holds true for nested references as well [A constructs B constructs C means neither B nor C will construct A]). I am currently working on the code that emits the constructors and I have a bit of an issue. I don't know the order of dependencies up-front, so it seems that I have a few options:
- Somehow sort the classes by their dependencies and 'build' them in the order of their dependencies, so the more dependent classes have a valid constructor reference to grab.
- Define all of the constructors separately in a first pass (without actually emitting the IL for the methods), so that all of the constructor references are defined.
- Somehow cache the defined constructors so that if the constructor has not yet been defined, I can create a place-holder ConstructorBuilder to get the reference, which will then be grabbed later when the constructor is finally emitted.
I'm currently attempting option (3) and I was wondering if there is already a way to do this from TypeBuilder. I have code that looks like this (to grab a constructor reference when its needed):
var fieldType = DefineType(udtField.Type); // This looks up a cached TypeBuilder or creates a placeholder, if needed
var constructor = fieldType.GetConstru开发者_开发问答ctor(Type.EmptyTypes);
if (constructor == null)
{
constructor =
fieldType.DefineConstructor(
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
CallingConventions.Standard, Type.EmptyTypes);
}
And my Build method currently starts like this (I don't think this will work if the constructor was previously defined):
private void BuildConstructor()
{
var method =
DefinedType.DefineConstructor(
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
CallingConventions.Standard, Type.EmptyTypes);
var il = method.GetILGenerator();
Is there some way that I can look up the ConstructorBuilder that was previously defined (if it has been), without having to create my own explicit cache? It seems like the TypeBuilder should know about it, but I can't see any obvious way to look it up from the TypeBuilder documentation.
EDIT:
I've ended up going down route (2), which defines all of the relevant methods during a first pass, then emits the IL afterward in a second pass. I'd still be curious if it's possible to get MethodBuilder instances (or ConstructorBuilder instances) from the TypeBuilder for builders that have already been defined elsewhere.
I am no expert on TypeBuilder
but it has a method .GetConstructor
( http://msdn.microsoft.com/en-us/library/cs01xzbk.aspx ) and .GetMethod
( http://msdn.microsoft.com/en-us/library/4s2kzbw8.aspx ) which should be able to return the declared constructor if you call them on your cached fieldType...
By looking at the disassembled code of TypeBuilder, it appears it's not possible it you want more than one constructor per type :
TypeBuilder.DefineConstructor
just calls DefineConstructorNoLock
which just checks parameters and increments the constructorCount field :
[SecurityCritical]
private ConstructorBuilder DefineConstructorNoLock(MethodAttributes attributes, CallingConventions callingConvention, Type[] parameterTypes, Type[][] requiredCustomModifiers, Type[][] optionalCustomModifiers)
{
this.CheckContext(parameterTypes);
this.CheckContext(requiredCustomModifiers);
this.CheckContext(optionalCustomModifiers);
this.ThrowIfCreated();
string name;
if ((attributes & MethodAttributes.Static) == MethodAttributes.PrivateScope)
{
name = ConstructorInfo.ConstructorName;
}
else
{
name = ConstructorInfo.TypeConstructorName;
}
attributes |= MethodAttributes.SpecialName;
ConstructorBuilder result = new ConstructorBuilder(name, attributes, callingConvention, parameterTypes, requiredCustomModifiers, optionalCustomModifiers, this.m_module, this);
this.m_constructorCount++;
return result;
}
So if you just want to define one constructor per type, you can check this property (using reflection, since it's a private field) and check its value :
namespace ConsoleApplication7
{
static class TypeBuilderExtension
{
public static int GetConstructorCount(this TypeBuilder t)
{
FieldInfo constCountField = typeof(TypeBuilder).GetField("m_constructorCount", BindingFlags.NonPublic | BindingFlags.Instance);
return (int) constCountField.GetValue(t);
}
}
class Program
{
static void Main(string[] args)
{
AppDomain ad = AppDomain.CurrentDomain;
AssemblyBuilder ab = ad.DefineDynamicAssembly(new AssemblyName("toto.dll"), AssemblyBuilderAccess.RunAndSave);
ModuleBuilder mb = ab.DefineDynamicModule("toto.dll");
TypeBuilder tb = mb.DefineType("mytype");
Console.WriteLine("before constructor creation : " + tb.GetConstructorCount());
ConstructorBuilder cb = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[0]);
ILGenerator ilgen = cb.GetILGenerator();
ilgen.Emit(OpCodes.Ret);
Console.WriteLine("after constructor creation : " + tb.GetConstructorCount());
tb.CreateType();
ab.Save("toto.dll");
}
}
}
which outputs :
before constructor creation : 0
after constructor creation : 1
This won't give you the actual ConstructorBuilder, but you'll know you already defined it.
If you want to actually get the constructorBuilder, and you don't want to create too many overloads (1 for example), I'd go for your option 3 with an extension method:
static class TypeBuilderExtension
{
private static Dictionary<TypeBuilder, ConstructorBuilder> _cache = new Dictionary<TypeBuilder, ConstructorBuilder>();
public static ConstructorBuilder DefineMyConstructor(this TypeBuilder tb)
{
if (!_cache.ContainsKey(tb))
{
_cache.Add(tb, tb.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[0]));
}
return _cache[tb];
}
}
精彩评论