Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using TypeBuilder to create a pass-through constructor for the base class

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!

like image 325
Cameron Avatar asked Jul 29 '11 21:07

Cameron


People also ask

How would you pass values to a base class constructor?

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.

Can base class have constructor?

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.

Can we override base class constructor?

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.


1 Answers

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!
like image 73
Cameron Avatar answered Sep 29 '22 05:09

Cameron