C#表达式动态实例化没有反射的类型

时间:2017-10-03 08:48:07

标签: c#

我正在尝试创建一种对象工厂来实例化一个实现特定接口的类型,其中实例化的类型在运行时之前是未知的,并且我试图在不使用反射的情况下实现这一点。我发现一些使用表达式可能可以实现这一点的例子,但我没有找到一个适用于我的案例的例子。可能是因为它根本不可能,但我想在这里提出问题以确保。

所以我到目前为止就是这样:

public static Func<Type, object[], IMyInterface> FactoryExpression =
    Expression.Lambda<Func<Type, object[], IMyInterface>>(
        /* Something that creates an instance of the type with given arguments */
    ).Compile()

public static IMyInterface GetTypeOfMyInterface()
{
    Type t = Type.GetType(GetTypeNameFromSomewhere());
    ConstructorInfo c = t.GetConstructors().First();
    object[] args = ResolveCostructorArguments(c.GetParameters());

    return FactoryExpression(t, args);
}

我对这些类型的表达式几乎没有任何表现。是否有可能让这个工作或我必须回到反思?

修改

通过使用Jon Hanna的例子,我想出了以下内容:

public class TypeInitializer<TResult>
{
    private static readonly ConcurrentDictionary<string, Func<object[], TResult>> InstanceCreationMethods =
        new ConcurrentDictionary<string, Func<object[], TResult>>();

    public static TResult CreateInstance(ConstructorInfo constructorInfo, params object[] arguments)
    {
        ParameterInfo[] parameterInfo = constructorInfo.GetParameters();
        IEnumerable<Type> parameterTypes = parameterInfo.Select(p => p.ParameterType);
        string constructorSignatureKey = GetConstructorSignatureKey(constructorInfo.DeclaringType, parameterTypes);

        Func<object[], TResult> factoryMethod = InstanceCreationMethods.GetOrAdd(constructorSignatureKey, key =>
                                                                                                         {
                                                                                                             Expression[] args = new Expression[parameterInfo.Length];
                                                                                                             ParameterExpression param = Expression.Parameter(typeof(object[]));
                                                                                                             for (int i = 0; i < parameterInfo.Length; i++)
                                                                                                                 args[i] = Expression.Convert(Expression.ArrayIndex(param, Expression.Constant(i)), parameterInfo[i].ParameterType);
                                                                                                             return Expression
                                                                                                                 .Lambda<Func<object[], TResult>>(Expression.Convert(Expression.New(constructorInfo, args), typeof(TResult)), param)
                                                                                                                 .Compile();
                                                                                                         });

        return factoryMethod(arguments);
    }

    private static string GetConstructorSignatureKey(Type type, IEnumerable<Type> argumentTypes) => string.Concat(type.FullName, " (", string.Join(", ", argumentTypes.Select(at => at.FullName)), ")");
}

这看起来像预期的那样有效!为此非常感谢。 为了实验,我还使用Activator.CreateInstance以及constructorInfo.Invoke实现了一个实现,并构建了som性能测试以查看差异。

00:00:00.9246614, Actiator
00:00:00.7524483, Constructor Invoke
00:00:00.8235814, Compiled Expression

此测试定时使用每种方法创建100 000个相同类型的实例并打印结果。我有点沮丧地看到Constructor Invoke方法接缝效果更好!

1 个答案:

答案 0 :(得分:2)

您可以使用Expression.New创建实例。

您还需要将ConstructorInfo传递给您的工厂,并使用Expression.Convert将对象强制转换为界面。

public static Func<object[], IMyInterface> BuildFactoryExpression(ConstructorInfo ctor)
{
    ParameterInfo[] par = ctor.GetParameters(); // Get the parameters of the constructor
    Expression[] args = new Expression[par.Length];
    ParameterExpression param = Expression.Parameter(typeof(object[])); // The object[] paramter to the Func
    for (int i = 0; i != par.Length; ++i)
    {
        // get the item from the array in the parameter and cast it to the correct type for the constructor
        args[i] = Expression.Convert(Expression.ArrayIndex(param, Expression.Constant(i)), par[i].ParameterType);
    }
    return Expression.Lambda<Func<object[], IMyInterface>>(
        // call the constructor and cast to IMyInterface.
        Expression.Convert(
            Expression.New(ctor, args)
        , typeof(IMyInterface)
        ), param
    ).Compile();
}
  

是的我用反射也解决了这个问题,这很简单。但在这种情况下,我希望尽可能避免性能损失。

这仍然是使用反射。如果您要反复使用相同的Func ,那么您正在Compile()编译为IL(例如非UWP)的上下文中运行,那么您可能会在这里获得,因为反射是曾经使用过一次来创建一个可重用的委托,从那时起就像你用C#方法编写委托一样。如果您可以输入您的参数而不是传递object[]并且必须从中进行投射,您将获得更多。

如果您一次使用这些代表,那么您也可以使用反射。如果您只是处于解释表达式委托的上下文中,那么无论如何它都必须在内部使用反射。