【问题标题】:Create compiled Expession.Lambda for types known only at runtime为仅在运行时已知的类型创建已编译的 Expession.Lambda
【发布时间】:2013-12-22 14:16:38
【问题描述】:

我正在尝试创建一个扩展方法,该方法生成和存储编译的 Lambda 表达式,并返回一个实例,其类型为泛型类型 T 的类,其中类和 T 仅在运行时已知(由用户驱动选择)。

由于我是表达式树的新手,我正在努力找出正确的语法。

下面的“makeGenericExpression”在运行时会出现以下错误

"静态方法需要空实例,非静态方法需要 非空实例。”

但 makeGenericLambda 也完全有可能存在缺陷(我只是还没接触过)

这是我目前所拥有的(我使用的是 C#4.0)

public static class TypeXtensions
{
    public static object GetGenericInstance<TArg>(this Type type, TArg argument)
    {
        return InstanceCreationFactory<TArg>
            .CreateGenericInstanceOf(type, argument);
    }

    private static class InstanceCreationFactory<TArg>
    {
        private static readonly Dictionary<Type, Func<TArg, object>> GenericInstanceCreationMethods =
            new Dictionary<Type, Func<TArg, object>>();

        public static object CreateGenericInstanceOf(Type type, TArg arg)
        {
            CacheGenericInstanceCreationMethodIfRequired(type);

            return GenericInstanceCreationMethods[type].Invoke(arg);
        }

        private static void CacheGenericInstanceCreationMethodIfRequired(Type type)
        {
            if (GenericInstanceCreationMethods.ContainsKey(type)) return;

            var makeGenericMI = type.GetType().GetMethod("MakeGenericType");
            var paramExpression = Expression.Parameter(typeof (TArg), "argument");

            var makeGenericExpression = Expression.Call(makeGenericMI, paramExpression);

            // Compile the Expression into a Func which takes the type argument and returns the constructed object:
            var makeGenericLambda = Expression
                .Lambda<Func<TArg, object>>(makeGenericExpression)
                .Compile();


            GenericInstanceCreationMethods[type] = makeGenericLambda;

        }

    }
}

为了完整起见,我正在使用的调用如下所示

    private void InputDataChanged(object sender, InputConnector<IndicatorStrategy>.InputDataChangedEventArgs e)
    {
        var baseType = e.Payload.GetType().BaseType;
        if (baseType == null) return;
        var arg = baseType.GetGenericArguments()[0];
        var test = typeof (ComplexIndicatorDataGenerator<>).GetGenericInstance(arg);
    }

【问题讨论】:

  • 我很困惑,arg 总是Type,那为什么TArg 是通用的?
  • @svick 因为 TArg 可以是 10 个不同的类中的任何一个,具体取决于用户在运行时的选择,并且扩展方法中的“this”可以是 4 种类型中的任何一种,直到运行时才知道。在我的示例中,我试图创建一个 ComplexIndicatorDataGenerator.
  • 这不是您的代码所做的。 TArg 永远是 Type
  • @svick 你说得对。您有什么建议可以让我实现使用(尝试的)表达式树方法生成 MyClass 实例的意图吗?

标签: c#-4.0 generics lambda expression-trees


【解决方案1】:

如果我正确理解你想要什么,你有两个Types,我们称它们为T1&lt;&gt;T2,你想创建一个T1&lt;T2&gt; 类型的实例。

为此,您不需要任何表达式树,只需直接调用MakeGenericType(),然后使用Activator.CreateInstance()

public static object CreateGenericInstance(
    Type genericTypeDefinition, Type genericParameter)
{
    var genericType = genericTypeDefinition.MakeGenericType(genericParameter);

    return Activator.CreateInstance(genericType);
}

如果Activator.CreateInstance() 对您来说太慢(并且您应该测量它实际上太慢了),那么您可以将其替换为缓存的 lambda:

Dictionary<Tuple<Type, Type>, Func<object>> instanceCreatorCache
    = new  Dictionary<Tuple<Type, Type>, Func<object>>();

object CreateGenericInstance(Type genericTypeDefinition, Type genericParameter)
{
    Func<object> instanceCreator;

    var cacheKey = Tuple.Create(genericTypeDefinition, genericParameter);

    if (!instanceCreatorCache.TryGetValue(cacheKey, out instanceCreator))
    {
        var genericType = genericTypeDefinition.MakeGenericType(genericParameter);

        instanceCreator = Expression.Lambda<Func<object>>(
            Expression.New(genericType)).Compile();

        instanceCreatorCache[cacheKey] = instanceCreator;
    }

    return instanceCreator();
}

当类型已经在缓存中时,我决定也避免调用MakeGenericType(),但我不知道这是否真的会提高性能。

【讨论】:

  • 这正是我想要的。谢谢!我会按照您的建议检查创建时间。目前只需要少量的实例。您的缓存 lambda 方法实际上包含让我难过的语法。我一直在努力理解如何将通用组件传递给 Expression.New。现在我看到了,这很明显。再次感谢您对此的快速帮助。问候
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-07
相关资源
最近更新 更多