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
}
}