为ctor

时间:2015-08-07 14:49:56

标签: c# reflection expression-trees

我正在尝试创建通用工厂类。由于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;。 为什么?我该如何解决这个问题?

1 个答案:

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

快乐的编码!