为ctor动态创建委托
dynamically create delegate for ctor
我正在尝试创建通用工厂 class。由于 Activator.CreateInstance 非常慢,我决定使用委托。目标是调用构造函数任何 public 构造函数,而不考虑参数数量。所以我是这样的:
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 方法中调用委托。没有参数一切顺利,但是当我添加一些参数时 - 我得到 InvalidOperationException - "variable '' of type 'System.Object[]' referenced from scope '', but it is not defined",在 foo.Compile() 调用之后。
为什么?我该如何解决这个问题?
下面是一个 class,它公开了一个扩展方法,该方法为您提供了一个委托,用于通过调用绑定到指定 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;
}
}
要使用它,例如假设您有这个 class:
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 和 DynamicInvoke API,而是使用 my
Func <Object[], Object>.
为什么?以下是我现在想到的几个优点:
1) DynamicInvoke 比调用直接类型化委托慢得多。
2) DynamicInvoke 将在出现异常时中断任何堆栈跟踪。我的意思是,无论何时在构造函数中抛出异常,您都会收到 TargetInvocationException 而不是真正发生的异常。您可以检查那个 TargetInvocationException 的 InnerException,但是……显然还有更多工作要做。直接调用类型化委托 Func 将使您免于此问题。
编码愉快!
我正在尝试创建通用工厂 class。由于 Activator.CreateInstance 非常慢,我决定使用委托。目标是调用构造函数任何 public 构造函数,而不考虑参数数量。所以我是这样的:
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 方法中调用委托。没有参数一切顺利,但是当我添加一些参数时 - 我得到 InvalidOperationException - "variable '' of type 'System.Object[]' referenced from scope '', but it is not defined",在 foo.Compile() 调用之后。 为什么?我该如何解决这个问题?
下面是一个 class,它公开了一个扩展方法,该方法为您提供了一个委托,用于通过调用绑定到指定 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;
}
}
要使用它,例如假设您有这个 class:
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 和 DynamicInvoke API,而是使用 my
Func <Object[], Object>.
为什么?以下是我现在想到的几个优点:
1) DynamicInvoke 比调用直接类型化委托慢得多。
2) DynamicInvoke 将在出现异常时中断任何堆栈跟踪。我的意思是,无论何时在构造函数中抛出异常,您都会收到 TargetInvocationException 而不是真正发生的异常。您可以检查那个 TargetInvocationException 的 InnerException,但是……显然还有更多工作要做。直接调用类型化委托 Func 将使您免于此问题。
编码愉快!