Say I have a SpaceShip
class, like so:
public class SpaceShip {
public SpaceShip() { }
public SpaceShip(IRocketFuelSource fuelSource) { }
}
I want to use TypeBuilder
to create a type at run-time which inherits from SpaceShip, and defines one constructor for each of the ones in SpaceShip. I don't need the constructors to actually do anything except pass their arguments up to the parent ("pass-through" constructors). For example, the generated type would look something like this if expressed in C#:
public class SpaceShipSubClass : SpaceShip {
public SpaceShipSubClass() : base() { }
public SpaceShipSubClass(IRocketFuelSource fuelSource) : base(fuelSource) { }
}
To complicate things a bit, I don't actually know which class the generated type will be inheriting from until run-time (so I'll have to take into account any number of constructors, possibly with default parameters).
Is this possible? I think I could figure it out if I had a general direction to start with, it's just that I'm completely new to TypeBuilder
.
Thanks!
To pass arguments to a constructor in a base class, use an expanded form of the derived class' constructor declaration, which passes arguments along to one or more base class constructors. Here, base1 through baseN are the names of the base classes inherited by the derived class.
In C#, both the base class and the derived class can have their own constructor. The constructor of a base class used to instantiate the objects of the base class and the constructor of the derived class used to instantiate the object of the derived class.
Constructor looks like method but it is not. It does not have a return type and its name is same as the class name. But, a constructor cannot be overridden.
Alright, I couldn't find anything online, so I ended up implementing my own. This should help start off anyone writing some sort of proxy, too.
public static class TypeBuilderHelper
{
/// <summary>Creates one constructor for each public constructor in the base class. Each constructor simply
/// forwards its arguments to the base constructor, and matches the base constructor's signature.
/// Supports optional values, and custom attributes on constructors and parameters.
/// Does not support n-ary (variadic) constructors</summary>
public static void CreatePassThroughConstructors(this TypeBuilder builder, Type baseType)
{
foreach (var constructor in baseType.GetConstructors()) {
var parameters = constructor.GetParameters();
if (parameters.Length > 0 && parameters.Last().IsDefined(typeof(ParamArrayAttribute), false)) {
//throw new InvalidOperationException("Variadic constructors are not supported");
continue;
}
var parameterTypes = parameters.Select(p => p.ParameterType).ToArray();
var requiredCustomModifiers = parameters.Select(p => p.GetRequiredCustomModifiers()).ToArray();
var optionalCustomModifiers = parameters.Select(p => p.GetOptionalCustomModifiers()).ToArray();
var ctor = builder.DefineConstructor(MethodAttributes.Public, constructor.CallingConvention, parameterTypes, requiredCustomModifiers, optionalCustomModifiers);
for (var i = 0; i < parameters.Length; ++i) {
var parameter = parameters[i];
var parameterBuilder = ctor.DefineParameter(i + 1, parameter.Attributes, parameter.Name);
if (((int)parameter.Attributes & (int)ParameterAttributes.HasDefault) != 0) {
parameterBuilder.SetConstant(parameter.RawDefaultValue);
}
foreach (var attribute in BuildCustomAttributes(parameter.GetCustomAttributesData())) {
parameterBuilder.SetCustomAttribute(attribute);
}
}
foreach (var attribute in BuildCustomAttributes(constructor.GetCustomAttributesData())) {
ctor.SetCustomAttribute(attribute);
}
var emitter = ctor.GetILGenerator();
emitter.Emit(OpCodes.Nop);
// Load `this` and call base constructor with arguments
emitter.Emit(OpCodes.Ldarg_0);
for (var i = 1; i <= parameters.Length; ++i) {
emitter.Emit(OpCodes.Ldarg, i);
}
emitter.Emit(OpCodes.Call, constructor);
emitter.Emit(OpCodes.Ret);
}
}
private static CustomAttributeBuilder[] BuildCustomAttributes(IEnumerable<CustomAttributeData> customAttributes)
{
return customAttributes.Select(attribute => {
var attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray();
var namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<PropertyInfo>().ToArray();
var namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray();
var namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<FieldInfo>().ToArray();
var namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray();
return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues);
}).ToArray();
}
}
Usage (assuming you have a TypeBuilder
object -- see here for an example):
var typeBuilder = ...; // TypeBuilder for a SpaceShipSubClass
typeBuilder.CreatePassThroughConstructors(typeof(SpaceShip));
var subType = typeBuilder.CreateType(); // Woo-hoo, proxy constructors!
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