【问题标题】:Expression Tree for Enumerable.SelectEnumerable.Select 的表达式树
【发布时间】:2013-06-11 05:02:28
【问题描述】:

编辑我想我可以问得更好(在这种情况下根本不需要代码)。所以一般的问题是:在运行时创建TResult 时,如何使用表达式树来构建对通用方法(Select<TEntity,TResult> 的调用?忽略下面的所有代码和文本,这是问题的不清楚版本,不要让回答的人混淆。

我需要一个如何为“选择”调用构建表达式树的示例。问题是我在编译时不知道结果类型(它可以由用户在运行时通过一些 GUI 定义)。这是我尝试执行此操作的一些代码(请记住我不能使用任何泛型)

class Pet {
    public int Id { get; set; }
    public string Name { get; set; }
}

在 Main 中使用这个类:

List<Pet> list = new List<Pet>();                
Expression eList = Expression.Constant(list);
ParameterExpression pe = Expression.Parameter(typeof(Pet), "p");
MethodInfo method = typeof(Program).GetMethod("CreateObject", BindingFlags.Static | BindingFlags.NonPublic);
var properties = typeof(Pet).GetProperties().Where(pi => pi.Name == "Name"); //will be defined by user
Expression selectorBody = Expression.Call(method, Expression.Constant(properties));
Expression selector = Expression.Lambda(selectorBody, pe);
Expression res = Expression.Call(typeof(Enumerable), "Select", new[] { typeof(Pet), CreateType(properties) }, eList, selector);

我要做的是在运行时使用 Reflection.Emit 创建一个类型(上面代码中的方法“CreateType”,它在我的一些项目中使用并且看起来不错)并以某种方式将它传递给调用“Select ",但我得到了例外:

类型“System.Linq.Enumerable”上没有通用方法“Select”与提供的类型参数和参数兼容。

有什么建议吗?

更新我真正的问题是为更复杂的运行时类型的“加入”调用构建表达式树,所以我决定询问“选择”,因为我有同样的问题最后一行的异常(Expression.Call(...))

Upd2 包含了我的辅助方法并编辑了主代码,但这不是主要问题。

static Type CreateType(IEnumerable<PropertyInfo> properties) {
        TypeBuilder typeBuilder = CreateTypeBuilder("ResultDynamicAssembly", "ResultModule", "ResultType");
        foreach (PropertyInfo propertyInfo in properties) {
            CreateAutoImplementedProperty(typeBuilder, propertyInfo.Name, propertyInfo.PropertyType);
        }
        return typeBuilder.CreateType();
    }

    static object CreateObject(IEnumerable<PropertyInfo> properties) {
        Type type = CreateType(properties); 
        return Activator.CreateInstance(type);
    }

【问题讨论】:

  • 你能发布你的代码模式吗?如果您正在构建的表达式树很复杂,则几乎不可能推测出可能出现的问题。

标签: c# expression-trees


【解决方案1】:

您可以通过从方程式中删除动态类型来简化此问题。您可以使用下面的代码重现相同的问题,它的作用完全相同,但没有动态类型。

static class Program
{
    private static void Main(string[] args)
    {
        var list = new List<Pet>();
        var eList = Expression.Constant(list);
        var pe = Expression.Parameter(typeof(Pet), "p");
        var method = typeof(Program).GetMethod("CreateObject", BindingFlags.Static | BindingFlags.NonPublic);
        var properties = typeof(Pet).GetProperties().Where(pi => pi.Name == "Name"); //will be defined by user
        var selectorBody = Expression.Call(method, Expression.Constant(properties));
        var selector = Expression.Lambda(selectorBody, pe);
        var res = Expression.Call(typeof(Enumerable), "Select", new[] { typeof(Pet), CreateType(properties) }, eList, selector);
    }

    private static Type CreateType(IEnumerable<PropertyInfo> properties)
    {
        return typeof (DynamicType);
    }

    private static object CreateObject(IEnumerable<PropertyInfo> properties)
    {
        var type = CreateType(properties);
        return  Activator.CreateInstance(type);
    }

    class Pet
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    class DynamicType
    {
        public string Name { get; set; }
    }
}

所以问题出在 CreateObject 的方法签名上。由于它的返回类型不是动态类型,因此 lambda 无效。您可以通过更改CreateObject 的类型来看到这一点。

    // this works fine
    private static DynamicType CreateObject(IEnumerable<PropertyInfo> properties)
    {
        var type = CreateType(properties);
        return  (DynamicType) Activator.CreateInstance(type);
    }

由于您处理的是动态类型,因此您需要构建一个表达式以将CreateObject 的结果转换为您的动态类型。尝试使用这样的东西:

        // create dynamic type
        var properties = typeof(Pet).GetProperties().Where(pi => pi.Name == "Name"); //will be defined by user
        var dynamicType = CreateType(properties);

        // build expression
        var list = new List<Pet>();
        var eList = Expression.Constant(list);
        var pe = Expression.Parameter(typeof(Pet), "p");
        var createObjectMethod = typeof(Program).GetMethod("CreateObject", BindingFlags.Static | BindingFlags.NonPublic);
        var createObjectCall = Expression.Call(createObjectMethod, Expression.Constant(properties));
        var castExpression = Expression.Convert(createObjectCall, dynamicType);
        var selectorExpression = Expression.Lambda(castExpression, pe);
        var res = Expression.Call(typeof(Enumerable), "Select", new[] { typeof(Pet), dynamicType }, eList, selectorExpression);

【讨论】:

  • 感谢您的回答,但是在我的情况下,您的建议中的 DynamicType 无法明确声明。考虑以下场景:具有 Name、Phone、Country、Id 属性的客户类,用户可以选择其中的任何一个以包含在新类型中(例如使用复选框),我的目标是在运行时创建此类型并在调用中使用它到泛型方法
  • 我明白了。上面的代码只是表明问题并不直接源于您对动态类型的使用。如上所示添加Expression.Convert() 调用,您的问题将得到解决。如果不是,我们还需要更多细节。
【解决方案2】:

为每种类型创建一个工厂。你知道来源,所以它真的很简单:

interface PetResultFactory<T>
{
    string TypeName {get; }
    List<T> Transform(IEnumerable<Pet> pets);
}

您必须在容器中注册所有可能的类型,并在 UI 上为用户显示注册的类型。当用户选择一个类型时,您知道如何创建该类型,因为它在工厂中进行了描述。

【讨论】:

  • 这不能回答问题。该问题与构建表达式树特别相关。未知类型实际上并不是问题的重要部分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-22
相关资源
最近更新 更多