Click here to Skip to main content
15,892,697 members
Articles / Desktop Programming / WPF

RuntimeExtensionManagement

Rate me:
Please Sign up or sign in to vote.
5.00/5 (8 votes)
5 Nov 2012CPOL35 min read 20.6K   265   15  
Extend your objects at run-time and create really loosely-coupled applications.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using Pfz.DynamicObjects.Internal;

namespace Pfz.DynamicObjects
{
	/// <summary>
	/// Class responsible for building new classes at run-time.
	/// </summary>
	/// <typeparam name="T">The type of the user-instance that this dynamic-type will receive as argument.</typeparam>
	public sealed class DelegatedTypeBuilder<T>
	{
		#region Static area
			private static readonly Type[] _parameterTypes = new Type[]{typeof(T)};
			private static readonly ConstructorInfo _objectConstructor = typeof(object).GetConstructor(Type.EmptyTypes);
			private static readonly MethodInfo _dynamicActionInvoke = typeof(DelegatedDynamicAction<T>).GetMethod("Invoke");
		#endregion

		#region Private fields - used by many methods
			private int _id;
			private readonly DelegatedTypeBuilderConversions _conversions;
			internal readonly TypeBuilder _type;
			private readonly FieldBuilder _userInstanceField;
			private readonly FieldBuilder _actionsField;
			private ILGenerator _constructorGenerator;
			private List<Delegate> _actions = new List<Delegate>();
			private Delegate[] _arrayActions;
		#endregion
		
		#region Constructor
			/// <summary>
			/// Creates a new instance that will use the given conversions and that will
			/// generate types that implement the given interfaces.
			/// Note: It is up to you to add all the needed methods to make the interfaces work.
			/// </summary>
			public DelegatedTypeBuilder(DelegatedTypeBuilderConversions conversions, bool generateCollectibleAssembly=false, params Type[] interfacesToImplement)
			{
				if (!typeof(T).IsVisible)
					throw new ArgumentException(typeof(T).FullName + " must be public.");

				if (interfacesToImplement != null)
				{
					foreach(var interfaceType in interfacesToImplement)
					{
						if (interfaceType == null)
							throw new ArgumentException("interfacesToImplement can't have null values.");
							
						if (!interfaceType.IsVisible)
							throw new ArgumentException("interfacesToImplement can't have non-public interfaces.");
					}
				}
				
				int id = _id++;
				if (generateCollectibleAssembly)
				{
					var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Assembly"), AssemblyBuilderAccess.RunAndCollect);
					var module = assembly.DefineDynamicModule("Module");
					_type = module.DefineType("Class" + id, TypeAttributes.Public | TypeAttributes.Sealed, typeof(object), interfacesToImplement);
				}
				else
				{
					_type = _DynamicModule.DefineType("Class" + id, TypeAttributes.Public | TypeAttributes.Sealed, typeof(object), interfacesToImplement);
				}

				_conversions = conversions;

				_userInstanceField = _type.DefineField("<<userInstance>>", typeof(T), FieldAttributes.Private | FieldAttributes.InitOnly);
				_actionsField = _type.DefineField("<<actions>>", typeof(Delegate[]), FieldAttributes.Public | FieldAttributes.Static);
				var constructor = _type.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, _parameterTypes);
				var generator = constructor.GetILGenerator();
				generator.Emit(OpCodes.Ldarg_0);
				generator.Emit(OpCodes.Call, _objectConstructor);
				generator.Emit(OpCodes.Ldarg_0);
				generator.Emit(OpCodes.Ldarg_1);
				generator.Emit(OpCodes.Stfld, _userInstanceField);
				_constructorGenerator = generator;
				
				var createMethod = _type.DefineMethod("<<Create>>", MethodAttributes.Static | MethodAttributes.Public, typeof(object), _parameterTypes);
				generator = createMethod.GetILGenerator();
				generator.Emit(OpCodes.Ldarg_0);
				generator.Emit(OpCodes.Newobj, constructor);
				generator.Emit(OpCodes.Ret);
			}
		#endregion
		#region Methods
			#region _AddOrReuseHandler
				private int _AddOrReuseHandler(Delegate action)
				{
					int index = _actions.IndexOf(action);
					if (index != -1)
						return index;
						
					index = _actions.Count;
					_actions.Add(action);
					return index;
				}
			#endregion
			#region _LoadUserInstance
				private void _LoadUserInstance(ILGenerator generator)
				{
					generator.Emit(OpCodes.Ldarg_0);
					generator.Emit(OpCodes.Ldfld, _userInstanceField);
				}
			#endregion
			#region _ConvertParameter
				private void _ConvertParameter(ref _ActionsList disposableList, string parameterName, bool isOut, Type toType, Type fromType, ILGenerator generator, Action stackValueAction)
				{
					bool isByRef = fromType.IsByRef;
					if (isByRef != toType.IsByRef)
						throw new NotSupportedException("Conversion from ref parameters to non-ref parameters is not supported.");

					if (toType == typeof(void))
					{
						stackValueAction();

						if (fromType != typeof(void))
							generator.Emit(OpCodes.Pop);

						return;
					}

					if (fromType == typeof(void))
					{
						stackValueAction();
						generator.Emit(OpCodes.Ldnull);
						return;
					}

					var innerFromType = fromType;
					var innerToType = toType;
					if (isByRef)
					{
						innerFromType = fromType.GetElementType();
						innerToType = toType.GetElementType();
					}

					if (_conversions != null)
					{
						var conversion = _conversions.TryGetConverter(innerFromType, innerToType);
						if (conversion != null)
						{
							var actionIndex = _AddOrReuseHandler(conversion);
							var conversionType = conversion.GetType();

							if (!isOut)
							{
								// stacks the conversion action
								generator.Emit(OpCodes.Ldsfld, _actionsField);
								generator.Emit(OpCodes.Ldc_I4, actionIndex);
								generator.Emit(OpCodes.Ldelem, conversionType);

								if (conversionType.GetGenericTypeDefinition() == typeof(Func<,,>))
									generator.Emit(OpCodes.Ldstr, parameterName);

							}

							// stacks the value
							stackValueAction();

							LocalBuilder firstLocal = null;
							LocalBuilder newLocalFrom = null;
							LocalBuilder newLocalTo = null;
							if (isByRef)
							{
								firstLocal = generator.DeclareLocal(innerFromType);
								newLocalFrom = generator.DeclareLocal(innerFromType);
								newLocalTo = generator.DeclareLocal(innerToType);

								generator.Emit(OpCodes.Stloc, firstLocal);

								if (!isOut)
								{
									generator.Emit(OpCodes.Ldloc, firstLocal);
									generator.Emit(OpCodes.Ldobj, innerFromType);
								}
							}

							if (!isOut)
							{
								// does the call... the return is already stacked, so keep it there.
								generator.Emit(OpCodes.Callvirt, conversionType.GetMethod("Invoke"));
							}

							if (isByRef)
							{
								if (!isOut)
									generator.Emit(OpCodes.Stloc, newLocalTo);

								generator.Emit(OpCodes.Ldloca, newLocalTo);

								Action endAction =
									() =>
									{
										var conversion2 = _conversions.TryGetConverter(innerToType, innerFromType);
										if (conversion2 == null)
											throw new InvalidOperationException("When a conversion is available to a ref or out field, the opposite conversion must also be available. Missing: " + innerToType.FullName + " to " + innerFromType.FullName);

										var actionIndex2 = _AddOrReuseHandler(conversion2);
										var conversionType2 = conversion2.GetType();

										// this is needed before everything to keep the stacked values
										// in order.
										generator.Emit(OpCodes.Ldloc, firstLocal);

										// stacks the conversion action
										generator.Emit(OpCodes.Ldsfld, _actionsField);
										generator.Emit(OpCodes.Ldc_I4, actionIndex2);
										generator.Emit(OpCodes.Ldelem, conversionType2);

										if (conversionType2.GetGenericTypeDefinition() == typeof(Func<,,>))
											generator.Emit(OpCodes.Ldstr, parameterName);

										// gets the value
										generator.Emit(OpCodes.Ldloc, newLocalTo);

										// does the call.
										generator.Emit(OpCodes.Callvirt, conversionType2.GetMethod("Invoke"));

										// Now we must store the returned value into the address of the initial variable.
										generator.Emit(OpCodes.Stobj, innerFromType);
									};

								if (disposableList == null)
									disposableList = new _ActionsList();

								disposableList.Add(endAction);
							}

							return;
						}
					}

					if (fromType == toType)
					{
						stackValueAction();
						return;
					}

					if (isByRef)
					{
						if (isOut && innerFromType.IsAssignableFrom(innerToType) && !innerToType.IsValueType)
						{
							stackValueAction();
							return;
						}

						throw new NotSupportedException("At this moment only explicit conversions are accepted to ref and out parameters or out parameters that use a more base type for reference-types.");
					}

					// you try to put a Disposable (maybe struct) into an IDisposable.
					if (toType.IsAssignableFrom(fromType))
					{
						stackValueAction();

						if (fromType.IsValueType)
							generator.Emit(OpCodes.Box, fromType);

						return;
					}

					// you try to pass an object to an string. A cast may solve the case.
					if (fromType.IsAssignableFrom(toType))
					{
						stackValueAction();
						generator.Emit(OpCodes.Castclass, toType);

						if (toType.IsValueType)
							generator.Emit(OpCodes.Unbox_Any, toType);

						return;
					}

					throw new ArgumentException("There is no valid conversion from type " + fromType.FullName + " to type " + toType.FullName);
				}
			#endregion
			#region _LoadParameters
				private _ActionsList _LoadParameters(ParameterInfo[] actionParameters, Type[] receivedParameterTypes, ILGenerator generator)
				{
					int count = actionParameters.Length;

					if (receivedParameterTypes != null && receivedParameterTypes.Length != count)
						throw new ArgumentException("The action has a different number of parameter types than expected.");

					_ActionsList result = null;

					for (int i = 0; i < count; i++)
					{
						var parameter = actionParameters[i];
						var toType = parameter.ParameterType;
						var fromType = toType;
						bool isOut = parameter.IsOut;

						if (receivedParameterTypes != null)
							fromType = receivedParameterTypes[i];

						int iPlus1 = i + 1;
						_ConvertParameter(ref result, parameter.Name, isOut, toType, fromType, generator, () => generator.Emit(OpCodes.Ldarg, iPlus1));
					}

					return result;
				}
			#endregion
			
			#region AddStaticMethod
				/// <summary>
				/// Static methods do not receive the userInstance value so all the parameters they receive 
				/// are passed directly to the action. They are not really static when implemented.
				/// </summary>
				public MethodBuilder AddStaticMethod(string name, Delegate action)
				{
					if (name == null)
						throw new ArgumentNullException("name");

					if (action == null)
						throw new ArgumentNullException("action");

					int index = _AddOrReuseHandler(action);
					
					var actionMethod = action.Method;
					var returnType = actionMethod.ReturnType;
					var parameters = actionMethod.GetParameters();
					int parameterCount = parameters.Length;
					var actionParameterTypes = new Type[parameterCount];
					for(int i=0; i<parameterCount; i++)
						actionParameterTypes[i] = parameters[i].ParameterType;
					
					var method = _type.DefineMethod(name, MethodAttributes.Public | MethodAttributes.Virtual, returnType, actionParameterTypes);
					var generator = method.GetILGenerator();
					var actionType = action.GetType();
					
					generator.Emit(OpCodes.Ldsfld, _actionsField);
					generator.Emit(OpCodes.Ldc_I4, index);
					generator.Emit(OpCodes.Ldelem, actionType);

					using(_LoadParameters(parameters, null, generator))
						generator.Emit(OpCodes.Callvirt, actionType.GetMethod("Invoke"));
						
					generator.Emit(OpCodes.Ret);
					return method;
				}
			#endregion
			#region AddMethod
				/// <summary>
				/// Instance methods will call the action passing the userInstance as the first
				/// member (like the this on normal instance methods).
				/// </summary>
				public MethodBuilder AddMethod(string name, Delegate action)
				{
					return AddMethod(name, action, null, null);
				}
				
				/// <summary>
				/// Adds a method that will use the given action to execute and allows you to
				/// specify different return type and parameter types. The data-type conversions will
				/// be used in this case.
				/// </summary>
				public MethodBuilder AddMethod(string name, Delegate action, Type generatedReturnType, params Type[] receivedParameterTypes)
				{
					if (name == null)
						throw new ArgumentNullException("name");

					if (action == null)
						throw new ArgumentNullException("action");

					var actionMethod = action.Method;
					var actionReturnType = actionMethod.ReturnType;
					var parameters = actionMethod.GetParameters();
					int parameterCount = parameters.Length;
					if (parameterCount == 0)
						throw new ArgumentException("action must receive at least one parameter (the userInstance).", "action");
						
					int index = _AddOrReuseHandler(action);

					parameterCount--;
					var actionParameterTypes = new Type[parameterCount];
					var actionParameters = new ParameterInfo[parameterCount];
					for (int i = 0; i < parameterCount; i++)
					{
						var parameter = parameters[i+1];
						actionParameters[i] = parameter;
						actionParameterTypes[i] = parameter.ParameterType;
					}
						
					var typesToUse = receivedParameterTypes;
					if (typesToUse == null)
						typesToUse = actionParameterTypes;

					if (generatedReturnType == null)
						generatedReturnType = actionReturnType;

					var method = _type.DefineMethod(name, MethodAttributes.Public | MethodAttributes.Virtual, generatedReturnType, typesToUse);
					var generator = method.GetILGenerator();
					var actionType = action.GetType();

					_ActionsList disposableList = null;
					try
					{
						_ConvertParameter
						(
							ref disposableList,
							"return",
							false,
							generatedReturnType, actionReturnType, generator,
							() =>
							{
								generator.Emit(OpCodes.Ldsfld, _actionsField);
								generator.Emit(OpCodes.Ldc_I4, index);
								generator.Emit(OpCodes.Ldelem, actionType);

								var firstParameter = parameters[0];
								_ConvertParameter(ref disposableList, firstParameter.Name, false, firstParameter.ParameterType, typeof(T), generator, () => _LoadUserInstance(generator));
								
								using(_LoadParameters(actionParameters, receivedParameterTypes, generator))
									generator.Emit(OpCodes.Callvirt, actionType.GetMethod("Invoke"));
							}
						);
					}
					finally
					{
						if (disposableList != null)
							disposableList.Dispose();
					}
					
					generator.Emit(OpCodes.Ret);
					
					return method;
				}
			#endregion
			#region AddDynamicMethod
				/// <summary>
				/// Adds a "dynamic method". That is, the method is added normally, but will call an action
				/// capable of executing independent on the number of arguments.
				/// Such dynamism makes things a little slower, though.
				/// </summary>
				public MethodBuilder AddDynamicMethod(string name, DelegatedDynamicAction<T> action, Type returnType, params Type[] parameterTypes)
				{
					if (name == null)
						throw new ArgumentNullException("name");
						
					if (returnType == null)
						returnType = typeof(void);
						
					if (parameterTypes != null)
						foreach(var parameterType in parameterTypes)
							if (parameterType == null || parameterType == typeof(void))
								throw new ArgumentException("parameterTypes can't contain null values or typeof(void).", "parameterTypes");
								
					if (action == null)
						throw new ArgumentNullException("action");
						
					int actionIndex = _AddOrReuseHandler(action);

					var result = _type.DefineMethod(name, MethodAttributes.Public | MethodAttributes.Virtual, returnType, parameterTypes);
					var generator = result.GetILGenerator();

					// stack the action itself.
					generator.Emit(OpCodes.Ldsfld, _actionsField);
					generator.Emit(OpCodes.Ldc_I4, actionIndex);
					generator.Emit(OpCodes.Ldelem, typeof(DelegatedDynamicAction<T>));
					
					// stack the userInstance.
					generator.Emit(OpCodes.Ldarg_0);
					generator.Emit(OpCodes.Ldfld, _userInstanceField);

					// stack the values
					if (parameterTypes == null || parameterTypes.Length == 0)
						generator.Emit(OpCodes.Ldnull);
					else
					{
						int count = parameterTypes.Length;
						generator.Emit(OpCodes.Ldc_I4, count);
						generator.Emit(OpCodes.Newarr, typeof(object));
						
						var arrayVariable = generator.DeclareLocal(typeof(object[]));
						generator.Emit(OpCodes.Stloc, arrayVariable);
						for(int i=0; i<count; i++)
						{
							generator.Emit(OpCodes.Ldloc, arrayVariable);
							generator.Emit(OpCodes.Ldc_I4, i);
							generator.Emit(OpCodes.Ldarg, i+1);
							
							var parameterType = parameterTypes[i];
							if (parameterType.IsValueType)
								generator.Emit(OpCodes.Box, parameterType);
								
							generator.Emit(OpCodes.Stelem, typeof(object));
						}
						
						generator.Emit(OpCodes.Ldloc, arrayVariable);
					}

					generator.Emit(OpCodes.Callvirt, _dynamicActionInvoke);

					if (returnType == typeof(void))
						generator.Emit(OpCodes.Pop);
					else
					if (returnType.IsValueType)
						generator.Emit(OpCodes.Unbox_Any, returnType);

					generator.Emit(OpCodes.Ret);
					return result;
				}
			#endregion
			#region AddProperty
				/// <summary>
				/// Adds a property that will redirect to the given get or set actions.
				/// If one of the values is null, the getter or setter will not be generated.
				/// </summary>
				public PropertyBuilder AddProperty<TProperty>(string name, Func<T, TProperty> getAction, Action<T, TProperty> setAction)
				{
					return AddProperty(name, typeof(TProperty), getAction, setAction);
				}
				
				/// <summary>
				/// Adds a property that will redirect to ghe given get or set, but allows getters and setters
				/// of different types to be given. The Conversions will be used if the types are different.
				/// </summary>
				public PropertyBuilder AddProperty(string name, Type propertyType, Delegate getAction, Delegate setAction)
				{
					if (string.IsNullOrEmpty(name))
						throw new ArgumentNullException("name");
						
					if (getAction == null && setAction == null)
						throw new ArgumentException("getAction and setAction can't be both null.");

					var result = _type.DefineProperty(name, PropertyAttributes.None, propertyType, Type.EmptyTypes);
					if (getAction != null)
					{
						var getMethod = AddMethod("get_" + name, getAction, propertyType, null);
						result.SetGetMethod(getMethod);
					}

					if (setAction != null)
					{
						var setMethod = AddMethod("set_" + name, setAction, typeof(void), propertyType);
						result.SetSetMethod(setMethod);
					}
						
					return result;
				}
			#endregion
			#region AddPropertyWithField
				/// <summary>
				/// Adds a property with its respective field (for fast access).
				/// It can then call a get method (providing the fieldValue) and/or a set method (providing the field 
				/// value as a reference and a new value). If those are not provided, the direct get/set will be
				/// implemented.
				/// </summary>
				/// <typeparam name="TProperty">The type of the property to create.</typeparam>
				/// <param name="name">The name of the property to create.</param>
				/// <param name="getAction">The action to invoke for get. If it is not provided, it will be auto-implemented.</param>
				/// <param name="setAction">The action to invoke for set. If it is not provided, it will be auto-implemented.</param>
				/// <param name="initializeAction">An action used to initialize the field (if needed).</param>
				/// <returns>The generated PropertyBuilder.</returns>
				public PropertyBuilder AddPropertyWithField<TProperty>(string name, DelegatedPropertyWithFieldGet<T, TProperty, TProperty> getAction, DelegatedPropertyWithFieldSet<T, TProperty, TProperty> setAction, DelegatedFieldInitialize<T, TProperty> initializeAction)
				{
					return AddPropertyWithField<TProperty, TProperty>(name, getAction, setAction, initializeAction);
				}

				// TODO make this version support delegates in general.
				// TODO check the types and, if they are ok, do the actual implementation. This will make
				// the lazy loader generator more versatile (and really useable).
				/// <summary>
				/// Adds a property with its respective field (for fast access).
				/// It can then call a get method (providing the fieldValue) and/or a set method (providing the field 
				/// value as a reference and a new value). If those are not provided, the direct get/set will be
				/// implemented.
				/// </summary>
				/// <typeparam name="TProperty">The type of the property to create.</typeparam>
				/// <typeparam name="TField">The type of the field to create, which can be different from the property type.</typeparam>
				/// <param name="name">The name of the property to create.</param>
				/// <param name="getAction">The action to invoke for get. If it is not provided, it will be auto-implemented if TField and TProperty are the same type.</param>
				/// <param name="setAction">The action to invoke for set. If it is not provided, it will be auto-implemented if TField and TProperty are the same type.</param>
				/// <param name="initializeAction">An action used to initialize the field (if needed).</param>
				/// <returns>The generated PropertyBuilder.</returns>
				public PropertyBuilder AddPropertyWithField<TProperty, TField>(string name, DelegatedPropertyWithFieldGet<T, TProperty, TField> getAction, DelegatedPropertyWithFieldSet<T, TProperty, TField> setAction, DelegatedFieldInitialize<T, TField> initializeAction)
				{
					if (string.IsNullOrEmpty(name))
						throw new ArgumentNullException("name");
						
					if (getAction == null && setAction == null && typeof(TProperty) != typeof(TField))
						throw new ArgumentException("getAction and setAction can't be null if TField and TProperty are different.");

					var result = _type.DefineProperty(name, PropertyAttributes.None, typeof(TProperty), Type.EmptyTypes);
					var field = _type.DefineField("<<field>>" + name, typeof(TField), FieldAttributes.Private);
					if (getAction != null || typeof(TProperty) == typeof(TField))
					{
						var getMethod = _type.DefineMethod("get_" + name, MethodAttributes.Public | MethodAttributes.Virtual, typeof(TProperty), Type.EmptyTypes);
						var getGenerator = getMethod.GetILGenerator();
						
						if (getAction == null)
						{
							// only loads the field value.
							getGenerator.Emit(OpCodes.Ldarg_0);	
							getGenerator.Emit(OpCodes.Ldfld, field);
						}
						else
						{
							var actionIndex = _AddOrReuseHandler(getAction);

							// stack the action itself.
							getGenerator.Emit(OpCodes.Ldsfld, _actionsField);
							getGenerator.Emit(OpCodes.Ldc_I4, actionIndex);
							getGenerator.Emit(OpCodes.Ldelem, typeof(DelegatedDynamicAction<T>));

							// stack the userInstance.
							getGenerator.Emit(OpCodes.Ldarg_0);
							getGenerator.Emit(OpCodes.Ldfld, _userInstanceField);
							
							// stack the field value
							getGenerator.Emit(OpCodes.Ldarg_0);
							getGenerator.Emit(OpCodes.Ldfld, field);
							
							getGenerator.Emit(OpCodes.Callvirt, typeof(DelegatedPropertyWithFieldGet<T, TProperty, TField>).GetMethod("Invoke"));
						}
						getGenerator.Emit(OpCodes.Ret);
						result.SetGetMethod(getMethod);
					}
					
					if (setAction != null || typeof(TProperty) == typeof(TField))
					{
						var setMethod = _type.DefineMethod("set_" + name, MethodAttributes.Public | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(TProperty) });
						var setGenerator = setMethod.GetILGenerator();

						if (setAction == null)
						{
							setGenerator.Emit(OpCodes.Ldarg_0);
							setGenerator.Emit(OpCodes.Ldarg_1);
							setGenerator.Emit(OpCodes.Stfld, field);
						}
						else
						{
							var actionIndex = _AddOrReuseHandler(setAction);

							// stack the action itself.
							setGenerator.Emit(OpCodes.Ldsfld, _actionsField);
							setGenerator.Emit(OpCodes.Ldc_I4, actionIndex);
							setGenerator.Emit(OpCodes.Ldelem, typeof(DelegatedDynamicAction<T>));

							// stack the userInstance.
							setGenerator.Emit(OpCodes.Ldarg_0);
							setGenerator.Emit(OpCodes.Ldfld, _userInstanceField);

							// stack the field reference
							setGenerator.Emit(OpCodes.Ldarg_0);
							setGenerator.Emit(OpCodes.Ldflda, field);
							
							// stack the value passed as parameter
							setGenerator.Emit(OpCodes.Ldarg_1);

							setGenerator.Emit(OpCodes.Callvirt, typeof(DelegatedPropertyWithFieldSet<T, TProperty, TField>).GetMethod("Invoke"));
						}
						setGenerator.Emit(OpCodes.Ret);
						result.SetSetMethod(setMethod);
					}
					
					if (initializeAction != null)
					{
						var actionIndex = _AddOrReuseHandler(initializeAction);
						
						_constructorGenerator.Emit(OpCodes.Ldarg_0);

						// stack the action itself.
						_constructorGenerator.Emit(OpCodes.Ldsfld, _actionsField);
						_constructorGenerator.Emit(OpCodes.Ldc_I4, actionIndex);
						_constructorGenerator.Emit(OpCodes.Ldelem, typeof(DelegatedDynamicAction<T>));

						// stack the userInstance.
						_constructorGenerator.Emit(OpCodes.Ldarg_0);
						_constructorGenerator.Emit(OpCodes.Ldfld, _userInstanceField);

						_constructorGenerator.Emit(OpCodes.Callvirt, typeof(DelegatedFieldInitialize<T, TField>).GetMethod("Invoke"));
						_constructorGenerator.Emit(OpCodes.Stfld, field);
						
					}

					return result;
				}
			#endregion
			#region AddEvent
				/// <summary>
				/// Adds an event that will use the given actions to "add" and to "remove" handlers.
				/// </summary>
				public EventBuilder AddEvent<TEvent>(string name, DelegatedEventAddRemove<T, TEvent> addAction, DelegatedEventAddRemove<T, TEvent> removeAction)
				{
					if (string.IsNullOrEmpty(name))
						throw new ArgumentNullException("name");
						
					if (addAction == null)
						throw new ArgumentNullException("addAction");
						
					if (removeAction == null)
						throw new ArgumentNullException("removeAction");
						
					var result = _type.DefineEvent(name, EventAttributes.None, typeof(TEvent));
					
					var addMethod = AddMethod("add_" + name, addAction);
					result.SetAddOnMethod(addMethod);
					
					var removeMethod = AddMethod("remove_" + name, removeAction);
					result.SetRemoveOnMethod(removeMethod);
					return result;
				}
			#endregion

			#region AdaptToUserInstance
				/// <summary>
				/// This will create a method with the given name, returnType and parameterTypes that
				/// will call a method on the userInstance.
				/// Note: The afterAction is optional and maybe in the future it will accept extra parameters.
				/// </summary>
				public MethodBuilder AdaptToUserInstance(MethodInfo methodToCall, string name, Delegate getInnerInstance, Action<T> afterAction, Type returnType, params Type[] parameterTypes)
				{
					if (methodToCall == null)
						throw new ArgumentNullException("methodToCall");
						
					var userInstanceType = typeof(T);
					if (getInnerInstance != null)
					{
						var getInnerInstanceMethod = getInnerInstance.Method;
						userInstanceType = getInnerInstanceMethod.ReturnType;
						var getInnerInstanceParameters = getInnerInstanceMethod.GetParameters();
						if (getInnerInstanceParameters.Length != 1)
							throw new ArgumentException("getInnerInstance must point to a method that receive a single parameter.");
							
						if (getInnerInstanceParameters[0].ParameterType != typeof(T))
							throw new ArgumentException("getInnerInstance must point to a method that receives a single parameter of type " + typeof(T).FullName);
					}
					
					if (!methodToCall.IsStatic)
					{
						if (!methodToCall.ReflectedType.IsAssignableFrom(userInstanceType))
							throw new ArgumentException("methodToCall must be static or must be from type " + userInstanceType);
					}
						
					if (name == null)
						throw new ArgumentNullException("name");
						
					if (returnType == null)
						returnType = methodToCall.ReturnType;
					
					var methodParameters = methodToCall.GetParameters();
					int parameterCount = methodParameters.Length;
					var methodParameterTypes = new Type[parameterCount];
					for(int i=0; i<parameterCount; i++)
						methodParameterTypes[i] = methodParameters[i].ParameterType;
					
					if (parameterTypes == null)
						parameterTypes = methodParameterTypes;
					else	
					if (parameterTypes.Length != methodParameterTypes.Length)
						throw new ArgumentException("parameterTypes has the wrong number of values.");

					_ActionsList disposableList = null;
					var newMethod = _type.DefineMethod(name, MethodAttributes.Public | MethodAttributes.Virtual, returnType, parameterTypes);
					var generator = newMethod.GetILGenerator();
					
					try
					{
						_ConvertParameter
						(
							ref disposableList,
							"return",
							false,
							returnType,
							methodToCall.ReturnType,
							generator,
							() =>
							{
								if (!methodToCall.IsStatic)
								{
									if (getInnerInstance != null)
									{
										var getInnerInstanceType = getInnerInstance.GetType();
										var getInnerInstanceIndex = _AddOrReuseHandler(getInnerInstance);
										generator.Emit(OpCodes.Ldsfld, _actionsField);
										generator.Emit(OpCodes.Ldc_I4, getInnerInstanceIndex);
										generator.Emit(OpCodes.Ldelem, getInnerInstanceType);
										
										_LoadUserInstance(generator);
										
										generator.Emit(OpCodes.Callvirt, getInnerInstanceType.GetMethod("Invoke"));

										if (userInstanceType.IsValueType)
										{
											if (methodToCall.DeclaringType == userInstanceType)
											{
												var local = generator.DeclareLocal(userInstanceType);
												generator.Emit(OpCodes.Stloc, local);
												generator.Emit(OpCodes.Ldloca, local);
											}
											else
												generator.Emit(OpCodes.Box, userInstanceType);
										}
									}
									else
									{
										generator.Emit(OpCodes.Ldarg_0);
										if (userInstanceType.IsValueType)
										{
											if (methodToCall.DeclaringType == userInstanceType)
												generator.Emit(OpCodes.Ldflda, _userInstanceField);
											else
											{
												generator.Emit(OpCodes.Ldfld, _userInstanceField);
												generator.Emit(OpCodes.Box, userInstanceType);
											}
										}
										else
											generator.Emit(OpCodes.Ldfld, _userInstanceField);
									}
								}
									
								using(_LoadParameters(methodParameters, parameterTypes, generator))
								{
									if (methodToCall.IsVirtual && !typeof(T).IsValueType)
										generator.Emit(OpCodes.Callvirt, methodToCall);
									else
										generator.Emit(OpCodes.Call, methodToCall);
								}
							}
						);
					}
					finally
					{
						if (disposableList != null)
							disposableList.Dispose();
					}
					
					if (afterAction != null)
					{
						var actionIndex = _AddOrReuseHandler(afterAction);

						generator.Emit(OpCodes.Ldsfld, _actionsField);
						generator.Emit(OpCodes.Ldc_I4, actionIndex);
						generator.Emit(OpCodes.Ldelem, typeof(Action<T>));
						
						_LoadUserInstance(generator);

						generator.Emit(OpCodes.Callvirt, typeof(Action<T>).GetMethod("Invoke"));
					}
					
					generator.Emit(OpCodes.Ret);
					
					return newMethod;
				}
			#endregion
			#region AdaptInstanceCreation
				/// <summary>
				/// This will create a method with the given name, returnType and parameterTypes that
				/// will call a method on the userInstance.
				/// Note: The afterAction is optional and maybe in the future it will accept extra parameters.
				/// </summary>
				public MethodBuilder AdaptInstanceCreation(ConstructorInfo constructorToCall, string newMethodName, Type newReturnType, params Type[] parameterTypes)
				{
					if (constructorToCall == null)
						throw new ArgumentNullException("constructorToCall");
						
					if (newMethodName == null)
						throw new ArgumentNullException("newMethodName");
						
					if (newReturnType == null)
						newReturnType = constructorToCall.DeclaringType;
					
					var methodParameters = constructorToCall.GetParameters();
					int parameterCount = methodParameters.Length;
					var methodParameterTypes = new Type[parameterCount];
					for(int i=0; i<parameterCount; i++)
						methodParameterTypes[i] = methodParameters[i].ParameterType;
					
					if (parameterTypes == null)
						parameterTypes = methodParameterTypes;
					else	
					if (parameterTypes.Length != methodParameterTypes.Length)
						throw new ArgumentException("parameterTypes has the wrong number of values.");

					_ActionsList disposableList = null;
					var newMethod = _type.DefineMethod(newMethodName, MethodAttributes.Public | MethodAttributes.Virtual, newReturnType, parameterTypes);
					var generator = newMethod.GetILGenerator();
					
					try
					{
						_ConvertParameter
						(
							ref disposableList,
							"return",
							false,
							newReturnType,
							constructorToCall.DeclaringType,
							generator,
							() =>
							{
								using(_LoadParameters(methodParameters, parameterTypes, generator))
									generator.Emit(OpCodes.Newobj, constructorToCall);
							}
						);
					}
					finally
					{
						if (disposableList != null)
							disposableList.Dispose();
					}
					
					generator.Emit(OpCodes.Ret);
					
					return newMethod;
				}
			#endregion
		
			#region CreateCreator
				private Func<T, object> _creator;
				/// <summary>
				/// Gets the functions responsable for creating new instances.
				/// After getting this function, this type-builder should not be used anymore (even
				/// this method should not be called twice).
				/// </summary>
				public Func<T, object> CreateCreator()
				{
					var creator = _creator;
					
					if (creator == null)
					{
						_arrayActions = _actions.ToArray();
						_actions = null;
						_constructorGenerator.Emit(OpCodes.Ret);
						_constructorGenerator = null;
						
						var generatedType = _type.CreateType();
						generatedType.GetField("<<actions>>").SetValue(null, _arrayActions);
						var createMethod = generatedType.GetMethod("<<Create>>");
						creator = (Func<T, object>)Delegate.CreateDelegate(typeof(Func<T, object>), createMethod);
						_creator = creator;
					}
					
					return creator;
				}
			#endregion
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions