I'm trying to create generic factory class. Since Activator.CreateInstance is pretty slow, I decided to use delegates. The goal was call constructor any public constructor, regardless of parameters count. So I was going like this:
public void Register<VType>(TKey key, params object[] args) where VType : TType
{
ConstructorInfo ci = typeof(VType).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.HasThis, args.Select(a => a.GetType()).ToArray(), new ParameterModifier[] { });
if (ci == null)
throw new InvalidOperationException(string.Format("Constructor for type '{0}' was not found.", typeof(VType)));
var pExp = Expression.Parameter(args.GetType());
var ctorParams = ci.GetParameters();
var expArr = new Expression[ctorParams.Length];
var p = new ParameterExpression[ctorParams.Length];
for (var i = 0; i < ctorParams.Length; i++)
{
var ctorType = ctorParams[i].ParameterType;
var pName = ctorParams[i].Name;
var argExp = Expression.ArrayIndex(pExp, Expression.Constant(i));
var argExpConverted = Expression.Convert(argExp, ctorType);
expArr[i] = argExpConverted;
p[i] = Expression.Parameter(args[i].GetType(), pName);
}
var foo = Expression.Lambda(Expression.New(ci, expArr), p);
Delegate constructorDelegate = foo.Compile();
FactoryMap.Add(key, constructorDelegate);
}
And then - call delegate in Create method. With no parameters all goes well, but when I'm adding some - I'm getting InvalidOperationException - "variable '' of type 'System.Object[]' referenced from scope '', but it is not defined", after foo.Compile() call. Why? How can I resolve this issue?
Below is a class that exposes an extention method that gives you a delegate for creating an instance of type T by calling the constructor that binds to specified paramArguments types.
public static class ConstructorCallExcentions
{
private static Dictionary<ConstructorInfo, Func<Object[], Object>> _constructors = new Dictionary<ConstructorInfo,Func<object[],object>> ();
private static object syncObject = new object();
public static Func<Object[], Object> CreateConstructor<T>(this T @this, params Type[] paramArguments)
{
ConstructorInfo cInfo = typeof(T).GetConstructor(paramArguments);
if (cInfo == null)
throw new NotSupportedException("Could not detect constructor having the coresponding parameter types");
Func<Object[], Object> ctor;
if (false == _constructors.TryGetValue (cInfo, out ctor))
{
lock (_constructors)
{
if (false == _constructors.TryGetValue (cInfo, out ctor))
{
// compile the call
var parameterExpression = Expression.Parameter(typeof(object[]), "arguments");
List<Expression> argumentsExpressions = new List<Expression>();
for (var i = 0; i < paramArguments.Length; i++)
{
var indexedAcccess = Expression.ArrayIndex(parameterExpression, Expression.Constant(i));
// it is NOT a reference type!
if (paramArguments [i].IsClass == false && paramArguments [i].IsInterface == false)
{
// it might be the case when I receive null and must convert to a structure. In this case I must put default (ThatStructure).
var localVariable = Expression.Variable(paramArguments[i], "localVariable");
var block = Expression.Block (new [] {localVariable},
Expression.IfThenElse (Expression.Equal (indexedAcccess, Expression.Constant (null)),
Expression.Assign (localVariable, Expression.Default (paramArguments [i])),
Expression.Assign (localVariable, Expression.Convert(indexedAcccess, paramArguments[i]))
),
localVariable
);
argumentsExpressions.Add(block);
}
else
argumentsExpressions.Add(Expression.Convert(indexedAcccess, paramArguments[i])); // do a convert to that reference type. If null, the convert is FINE.
}
// check if parameters length maches the length of constructor parameters!
var lengthProperty = typeof (Object[]).GetProperty ("Length");
var len = Expression.Property (parameterExpression, lengthProperty);
var invalidParameterExpression = typeof(ArgumentException).GetConstructor(new Type[] { typeof(string) });
var checkLengthExpression = Expression.IfThen (Expression.NotEqual (len, Expression.Constant (paramArguments.Length)),
Expression.Throw(Expression.New(invalidParameterExpression, Expression.Constant ("The length does not match parameters number")))
);
var newExpr = Expression.New(cInfo, argumentsExpressions);
var finalBlock = Expression.Block(checkLengthExpression, Expression.Convert(newExpr, typeof(Object)));
_constructors[cInfo] = ctor = Expression.Lambda(finalBlock, new[] { parameterExpression }).Compile() as Func<Object[], Object>;
}
}
}
return ctor;
}
}
To use it, for example supose you have this class:
public class Test
{
public Test(string s, int h)
{
Console.Write("aaa");
}
}
Then write this code:
var ctor = default(Test).CreateConstructor(typeof(string), typeof(int));
var newlyObject = ctor(new object[] { "john", 22 });
From your example, I saw that your intentions are to use the Delegate to invoke later any constructor. Instead of using Delegate and the DynamicInvoke API, use my
Func <Object[], Object>.
Why? Here is a couple of advantages that I have in mind right now:
1) DynamicInvoke is much slower than calling a direct typed delegate.
2) DynamicInvoke will break any stack trace in case of an exception. What I mean is that whenever an exception is thrown in the constructor, you will receive a TargetInvocationException instead of the real exception that happened. You can inspect the InnerException of that TargetInvocationException but ... clear is more work to do. Calling directly the typed delegate Func will save you from this issue.
Happy coding!