为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 将使您免于此问题。

编码愉快!