我正在尝试创建通用工厂类。由于Activator.CreateInstance很慢,我决定使用委托。无论参数计数如何,目标都是调用构造函数的任何公共构造函数。所以我想这样:
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);
}
然后 - 在Create方法中调用delegate。没有参数一切顺利,但当我添加一些 - 我得到InvalidOperationException - &#34;变量&#39;&#39;类型&#39; System.Object []&#39;从范围&#39;&#39;引用,但在foo.Compile()调用之后未定义&#34;。 为什么?我该如何解决这个问题?
答案 0 :(得分:1)
下面是一个暴露扩展方法的类,该方法通过调用绑定到指定paramArguments类型的构造函数为您提供创建类型T实例的委托。
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;
}
}
要使用它,例如,你有这个类:
public class Test
{
public Test(string s, int h)
{
Console.Write("aaa");
}
}
然后写下这段代码:
var ctor = default(Test).CreateConstructor(typeof(string), typeof(int));
var newlyObject = ctor(new object[] { "john", 22 });
从您的示例中,我看到您的意图是使用Delegate稍后调用任何构造函数。而不是使用Delegate和DynamicInvoke API,请使用我的
Func <Object[], Object>.
为什么呢?以下是我现在想到的几个优点:
1)DynamicInvoke比调用直接类型委托慢得多。
2)如果发生异常,DynamicInvoke将破坏任何堆栈跟踪。我的意思是,无论何时在构造函数中抛出异常,您将收到TargetInvocationException而不是发生的真实异常。您可以检查TargetInvocationException的InnerException,但是...... clear更多的工作要做。直接调用类型化的委托Func将使您免于此问题。
快乐的编码!