Creating a dynamic proxy generator with c# – Part 3 – Creating the constructors
- Creating a dynamic proxy generator with c# – Part 1 – Creating the Assembly builder, Module builder and caching mechanism
- Creating a dynamic proxy generator with c# – Part 2 – Interceptor Design
- Creating a dynamic proxy generator with c# – Part 4 – Calling the base method
For the latest code go to http://rapidioc.codeplex.com/
When building our proxy type, the first thing we need to do is build the constructors.
There needs to be a corresponding constructor for each constructor on the passed in base type.
We also want to create a field to store the interceptors and construct this list within each constructor.
So assuming the passed in base type is a User<int, IRepository> class, were looking to generate constructor code like the following:
- public User`2_RapidDynamicBaseProxy()
- {
- this.interceptors = new List<IInterceptor<User<int, IRepository>>>();
- DefaultInterceptor<User<int, IRepository>> item = new DefaultInterceptor<User<int, IRepository>>();
- this.interceptors.Add(item);
- }
- public User`2_RapidDynamicBaseProxy(IRepository repository1) : base(repository1)
- {
- this.interceptors = new List<IInterceptor<User<int, IRepository>>>();
- DefaultInterceptor<User<int, IRepository>> item = new DefaultInterceptor<User<int, IRepository>>();
- this.interceptors.Add(item);
- }
As you can see, we first populate a field on the class with a new list of the passed in base type.
Construct our DefaultInterceptor class.
Add the DefaultInterceptor instance to our interceptor collection.
Although this seems like a relatively small task, there is a fair amount of work require to get this going. Instead of going through every line of code – please download the latest from http://rapidioc.codeplex.com/ and debug through.
In this post I’m going to concentrate on explaining how it works.
TypeBuilder
The TypeBuilder class is the main class used to create the type.
You instantiate a new TypeBuilder using the assembly module we created in part 1.
- /// <summary>
- /// Creates a type builder.
- /// </summary>
- /// <typeparam name="TBase">The type of the base class to be proxied.</typeparam>
- public static TypeBuilder CreateTypeBuilder<TBase>() where TBase : class
- {
- TypeBuilder typeBuilder = DynamicModuleCache.Get.DefineType
- (
- CreateTypeName<TBase>(),
- TypeAttributes.Class | TypeAttributes.Public,
- typeof(TBase),
- new Type[] { typeof(IProxy) }
- );
- if (typeof(TBase).IsGenericType)
- {
- GenericsHelper.MakeGenericType(typeof(TBase), typeBuilder);
- }
- return typeBuilder;
- }
- private static string CreateTypeName<TBase>() where TBase : class
- {
- return string.Format("{0}_RapidDynamicBaseProxy", typeof(TBase).Name);
- }
As you can see, I’ve create a new public class derived from TBase which also implements my IProxy interface, this is used later for adding interceptors.
If the base type is generic, the following GenericsHelper.MakeGenericType method is called.
- using System;
- using System.Reflection.Emit;
- namespace Rapid.DynamicProxy.Types.Helpers
- {
- /// <summary>
- /// Helper class for generic types and methods.
- /// </summary>
- internal static class GenericsHelper
- {
- /// <summary>
- /// Makes the typeBuilder a generic.
- /// </summary>
- /// <param name="concrete">The concrete.</param>
- /// <param name="typeBuilder">The type builder.</param>
- public static void MakeGenericType(Type baseType, TypeBuilder typeBuilder)
- {
- Type[] genericArguments = baseType.GetGenericArguments();
- string[] genericArgumentNames = GetArgumentNames(genericArguments);
- GenericTypeParameterBuilder[] genericTypeParameterBuilder
- = typeBuilder.DefineGenericParameters(genericArgumentNames);
- typeBuilder.MakeGenericType(genericTypeParameterBuilder);
- }
- /// <summary>
- /// Gets the argument names from an array of generic argument types.
- /// </summary>
- /// <param name="genericArguments">The generic arguments.</param>
- public static string[] GetArgumentNames(Type[] genericArguments)
- {
- string[] genericArgumentNames = new string[genericArguments.Length];
- for (int i = 0; i < genericArguments.Length; i++)
- {
- genericArgumentNames[i] = genericArguments[i].Name;
- }
- return genericArgumentNames;
- }
- }
- }
As you can see, I’m getting all of the generic argument types and names, creating a GenericTypeParameterBuilder and then using the typeBuilder to make the new type generic.
InterceptorsField
The interceptors field will store a List<IInterceptor<TBase>>.
Fields are simple made using the FieldBuilder class.
The following code demonstrates how to create the interceptor field.
- FieldBuilder interceptorsField = typeBuilder.DefineField(
- "interceptors",
- typeof(System.Collections.Generic.List<>).MakeGenericType(typeof(IInterceptor<TBase>)),
- FieldAttributes.Private
- );
The field will now exist with the new Type although it currently has no data – we’ll deal with this in the constructor.
Add method for interceptorsField
To enable us to add to the interceptorsField list, we are going to utilise the Add method that already exists within the System.Collections.Generic.List class.
We still however have to create the methodInfo necessary to call the add method.
This can be done similar to the following:
- MethodInfo addInterceptor = typeof(List<>)
- .MakeGenericType(new Type[] { typeof(IInterceptor<>).MakeGenericType(typeof(TBase)) })
- .GetMethod
- (
- "Add",
- BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- null,
- new Type[] { typeof(IInterceptor<>).MakeGenericType(typeof(TBase)) },
- null
- );
So we’ve create a List<IInterceptor<TBase>> type, then using the type created a method info called Add which accepts an IInterceptor<TBase>.
Now in our constructor we can use this to call this.interceptors.Add(// interceptor);
Building the Constructors
This will be the first hard-core part of the proxy building process so I’m going to show the class and then try to explain what everything is doing.
For a clear view, download the source from http://rapidioc.codeplex.com/, go to the test project and debug through the constructor building section.
Anyway, here it is:
- using System;
- using System.Collections.Generic;
- using System.Reflection;
- using System.Reflection.Emit;
- using Rapid.DynamicProxy.Interception;
- using Rapid.DynamicProxy.Types.Helpers;
- namespace Rapid.DynamicProxy.Types.Constructors
- {
- /// <summary>
- /// Class for creating the proxy constructors.
- /// </summary>
- internal static class DynamicConstructorBuilder
- {
- /// <summary>
- /// Builds the constructors.
- /// </summary>
- /// <typeparam name="TBase">The base type.</typeparam>
- /// <param name="typeBuilder">The type builder.</param>
- /// <param name="interceptorsField">The interceptors field.</param>
- public static void BuildConstructors<TBase>
- (
- TypeBuilder typeBuilder,
- FieldBuilder interceptorsField,
- MethodInfo addInterceptor
- )
- where TBase : class
- {
- ConstructorInfo interceptorsFieldConstructor = CreateInterceptorsFieldConstructor<TBase>();
- ConstructorInfo defaultInterceptorConstructor = CreateDefaultInterceptorConstructor<TBase>();
- ConstructorInfo[] constructors = typeof(TBase).GetConstructors();
- foreach (ConstructorInfo constructorInfo in constructors)
- {
- CreateConstructor<TBase>
- (
- typeBuilder,
- interceptorsField,
- interceptorsFieldConstructor,
- defaultInterceptorConstructor,
- addInterceptor,
- constructorInfo
- );
- }
- }
- #region Private Methods
- private static void CreateConstructor<TBase>
- (
- TypeBuilder typeBuilder,
- FieldBuilder interceptorsField,
- ConstructorInfo interceptorsFieldConstructor,
- ConstructorInfo defaultInterceptorConstructor,
- MethodInfo AddDefaultInterceptor,
- ConstructorInfo constructorInfo
- ) where TBase : class
- {
- Type[] parameterTypes = GetParameterTypes(constructorInfo);
- ConstructorBuilder constructorBuilder = CreateConstructorBuilder(typeBuilder, parameterTypes);
- ILGenerator cIL = constructorBuilder.GetILGenerator();
- LocalBuilder defaultInterceptorMethodVariable =
- cIL.DeclareLocal(typeof(DefaultInterceptor<>).MakeGenericType(typeof(TBase)));
- ConstructInterceptorsField(interceptorsField, interceptorsFieldConstructor, cIL);
- ConstructDefaultInterceptor(defaultInterceptorConstructor, cIL, defaultInterceptorMethodVariable);
- AddDefaultInterceptorToInterceptorsList
- (
- interceptorsField,
- AddDefaultInterceptor,
- cIL,
- defaultInterceptorMethodVariable
- );
- CreateConstructor(constructorInfo, parameterTypes, cIL);
- }
- private static void CreateConstructor(ConstructorInfo constructorInfo, Type[] parameterTypes, ILGenerator cIL)
- {
- cIL.Emit(OpCodes.Ldarg_0);
- if (parameterTypes.Length > 0)
- {
- LoadParameterTypes(parameterTypes, cIL);
- }
- cIL.Emit(OpCodes.Call, constructorInfo);
- cIL.Emit(OpCodes.Ret);
- }
- private static void LoadParameterTypes(Type[] parameterTypes, ILGenerator cIL)
- {
- for (int i = 1; i <= parameterTypes.Length; i++)
- {
- cIL.Emit(OpCodes.Ldarg_S, i);
- }
- }
- private static void AddDefaultInterceptorToInterceptorsList
- (
- FieldBuilder interceptorsField,
- MethodInfo AddDefaultInterceptor,
- ILGenerator cIL,
- LocalBuilder defaultInterceptorMethodVariable
- )
- {
- cIL.Emit(OpCodes.Ldarg_0);
- cIL.Emit(OpCodes.Ldfld, interceptorsField);
- cIL.Emit(OpCodes.Ldloc, defaultInterceptorMethodVariable);
- cIL.Emit(OpCodes.Callvirt, AddDefaultInterceptor);
- }
- private static void ConstructDefaultInterceptor
- (
- ConstructorInfo defaultInterceptorConstructor,
- ILGenerator cIL,
- LocalBuilder defaultInterceptorMethodVariable
- )
- {
- cIL.Emit(OpCodes.Newobj, defaultInterceptorConstructor);
- cIL.Emit(OpCodes.Stloc, defaultInterceptorMethodVariable);
- }
- private static void ConstructInterceptorsField
- (
- FieldBuilder interceptorsField,
- ConstructorInfo interceptorsFieldConstructor,
- ILGenerator cIL
- )
- {
- cIL.Emit(OpCodes.Ldarg_0);
- cIL.Emit(OpCodes.Newobj, interceptorsFieldConstructor);
- cIL.Emit(OpCodes.Stfld, interceptorsField);
- }
- private static ConstructorBuilder CreateConstructorBuilder(TypeBuilder typeBuilder, Type[] parameterTypes)
- {
- return typeBuilder.DefineConstructor
- (
- MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName
- | MethodAttributes.HideBySig, CallingConventions.Standard, parameterTypes
- );
- }
- private static Type[] GetParameterTypes(ConstructorInfo constructorInfo)
- {
- ParameterInfo[] parameterInfoArray = constructorInfo.GetParameters();
- Type[] parameterTypes = new Type[parameterInfoArray.Length];
- for (int p = 0; p < parameterInfoArray.Length; p++)
- {
- parameterTypes[p] = parameterInfoArray[p].ParameterType;
- }
- return parameterTypes;
- }
- private static ConstructorInfo CreateInterceptorsFieldConstructor<TBase>() where TBase : class
- {
- return ConstructorHelper.CreateGenericConstructorInfo
- (
- typeof(List<>),
- new Type[] { typeof(IInterceptor<TBase>) },
- BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
- );
- }
- private static ConstructorInfo CreateDefaultInterceptorConstructor<TBase>() where TBase : class
- {
- return ConstructorHelper.CreateGenericConstructorInfo
- (
- typeof(DefaultInterceptor<>),
- new Type[] { typeof(TBase) },
- BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
- );
- }
- #endregion
- }
- }
So, the first two tasks within the class should be fairly clear, we are creating a ConstructorInfo for the interceptorField list and a ConstructorInfo for the DefaultConstructor, this is for instantiating them in each contructor.
We then using Reflection get an array of all of the constructors in the base class, we then loop through the array and create a corresponding proxy contructor.
Hopefully, the code is fairly easy to follow other than some new types and the dreaded Opcodes.
ConstructorBuilder
This class defines a new constructor on the type.
ILGenerator
The ILGenerator allows the use of Reflection.Emit to create the method body.
LocalBuilder
The local builder allows the storage of data in local variables within a method, in this case it’s the constructed DefaultInterceptor.
Constructing the interceptors field
The first bit of IL you’ll come across as you follow through the code is the following private method used for constructing the field list of interceptors.
- private static void ConstructInterceptorsField
- (
- FieldBuilder interceptorsField,
- ConstructorInfo interceptorsFieldConstructor,
- ILGenerator cIL
- )
- {
- cIL.Emit(OpCodes.Ldarg_0);
- cIL.Emit(OpCodes.Newobj, interceptorsFieldConstructor);
- cIL.Emit(OpCodes.Stfld, interceptorsField);
- }
The first thing to know about generating code using IL is that you are using a stack, if you want to use something, you need to push it up the stack etc. etc.
OpCodes.ldArg_0
This opcode is a really interesting one, basically each method has a hidden first argument of the containing class instance (apart from static classes), constructors are no different.
This is the reason you can use syntax like this.myField.
So back to the method, as we want to instantiate the List in the interceptorsField, first we need to load the class instance onto the stack, we then load the new object (new List<TBase>) and finally we store it in the interceptorsField.
Hopefully, that should follow easily enough in the method.
In each constructor you would now have
this.interceptors = new List<User<int, IRepository>>();
Constructing and storing the DefaultInterceptor
The next bit of code we need to create is the constructed DefaultInterceptor.
Firstly, we create a local builder to store the constructed type.
- LocalBuilder defaultInterceptorMethodVariable =
- cIL.DeclareLocal(typeof(DefaultInterceptor<>).MakeGenericType(typeof(TBase)));
Once our local builder is ready, we then need to construct the DefaultInterceptor<TBase> and store it in the variable.
- private static void ConstructDefaultInterceptor
- (
- ConstructorInfo defaultInterceptorConstructor,
- ILGenerator cIL,
- LocalBuilder defaultInterceptorMethodVariable
- )
- {
- cIL.Emit(OpCodes.Newobj, defaultInterceptorConstructor);
- cIL.Emit(OpCodes.Stloc, defaultInterceptorMethodVariable);
- }
As you can see, using the ConstructorInfo named defaultInterceptorConstructor, we load the new object onto the stack.
Then using the store local opcode (OpCodes.Stloc), we store the new object in the local builder named defaultInterceptorMethodVariable.
Add the constructed DefaultInterceptor to the interceptors field collection
Using the add method created earlier in this post, we are going to add the new DefaultInterceptor object to the interceptors field collection.
- private static void AddDefaultInterceptorToInterceptorsList
- (
- FieldBuilder interceptorsField,
- MethodInfo AddDefaultInterceptor,
- ILGenerator cIL,
- LocalBuilder defaultInterceptorMethodVariable
- )
- {
- cIL.Emit(OpCodes.Ldarg_0);
- cIL.Emit(OpCodes.Ldfld, interceptorsField);
- cIL.Emit(OpCodes.Ldloc, defaultInterceptorMethodVariable);
- cIL.Emit(OpCodes.Callvirt, AddDefaultInterceptor);
- }
So, here’s whats going on.
The class instance is first loaded onto the stack using the load argument at index 0 opcode (OpCodes.Ldarg_0) (remember the first arg is the hidden class instance).
The interceptorsField is then loaded onto the stack using the load field opcode (OpCodes.Ldfld).
We then load the DefaultInterceptor object we stored locally using the load local opcode (OpCodes.Ldloc).
Then finally we call the AddDefaultInterceptor method using the call virtual opcode (Opcodes.Callvirt).
Completing the constructor
The last thing we need to do is complete the constructor.
- private static void CreateConstructor(ConstructorInfo constructorInfo, Type[] parameterTypes, ILGenerator cIL)
- {
- cIL.Emit(OpCodes.Ldarg_0);
- if (parameterTypes.Length > 0)
- {
- LoadParameterTypes(parameterTypes, cIL);
- }
- cIL.Emit(OpCodes.Call, constructorInfo);
- cIL.Emit(OpCodes.Ret);
- }
- private static void LoadParameterTypes(Type[] parameterTypes, ILGenerator cIL)
- {
- for (int i = 1; i <= parameterTypes.Length; i++)
- {
- cIL.Emit(OpCodes.Ldarg_S, i);
- }
- }
So, the first thing we do again is load the class instance using the load argument at index 0 opcode (OpCodes.Ldarg_0).
We then load each parameter using OpCode.Ldarg_S, this opcode allows us to specify an index position for each argument.
We then setup calling the base constructor using OpCodes.Call and the base constructors ConstructorInfo.
Finally, all methods are required to return, even when they have a void return.
As there are no values on the stack after the OpCodes.Call line, we can safely call the OpCode.Ret to give the constructor a void return.
If there was a value, we would have to pop the value of the stack before calling return otherwise, the method would try and return a value.
Conclusion
This was a slightly hardcore post but hopefully it hasn’t been too hard to follow. The main thing is that a number of the really useful opcodes have been used and now the dynamic proxy is capable of being constructed.
If you download the code and debug through the tests at http://rapidioc.codeplex.com/, you’ll be able to create proxies at this point, they cannon do anything in terms of interception but you can happily run the tests, call base methods and properties and also take a look at the created assembly in Reflector.
Hope this is useful.
The next post should be up soon, it will be covering creating the private methods for calling the base class methods and properties.
Kind Regards,
Sean.