【问题标题】:Trouble implementing generic OrderBy solution难以实施通用 OrderBy 解决方案
【发布时间】:2017-04-12 12:06:11
【问题描述】:

我有一个采用IQueryable<T> 的方法,我想在其中通用地实现OrderBy。理想情况下,通过传入c => c.SomeProperty 作为参数,但我不知道如何让泛型工作,所以我用字符串尝试了它。但是我得到了错误:

Incorrect number of parameters supplied for lambda declaration

这是我尝试过的(使用字符串方法)

var sortSelectorParameter = Expression.Parameter(typeof(T), "c");
var sortSelector = Expression.PropertyOrField(sortSelectorParameter, "ClientId"); // ClientId is the property string

collection = collection.OrderByDescending(Expression.Lambda<Func<T, bool>>(sortSelector));

我很困惑,因为 OrderBy 只接受一个参数 - 有什么建议吗?

【问题讨论】:

    标签: c# generics expression-trees


    【解决方案1】:

    您需要将参数传递给Expression::Lambda&lt;T&gt;,如错误所示:

    var sortSelectorParameter = Expression.Parameter(typeof(T), "c");
    var sortSelector = Expression.PropertyOrField(sortSelectorParameter, "ClientId"); // ClientId is the property string
    
    collection = collection.OrderByDescending(Expression.Lambda<Func<T, bool>>(sortSelector, sortSelectorParameter ));
    

    您的 lambda 的“主体”指的是一个参数 c,它由 ExpressionParameter 实例 sortSelectorParameter 表示。您需要将此参数实例传递给 lambda,以便它知道 body 引用的参数实际上是您要创建的 lambda 的 in-parameter。

    编辑:以上内容可能会回答您的技术问题,但尚不清楚您要在这里实现什么。如果您只是想通过在编译时知道的东西来订购,那么您不需要任何这些。包装OrderByDescending-方法有什么意义?

    IQueryable<TElement> MySpecialOrderBy<TElement, TKey>(IQueryable<TElement> source, Expression<Func<TElement, TKey>> keySelector)
    {
        return source.OrderByDescending(keySelector);
    }
    

    【讨论】:

    • 啊,这很有道理。您是否知道我如何将参数传递为 c =&gt; c.SomeParam 而不是“SomeParam”?
    • 这会抛出异常,除非ClientId的类型是bool
    • @NibblyPig 如果它是没有约束的泛型,那么你不能。它怎么会在编译时知道T 会有一个属性SomeParam?您可以使用具有SomeParam 作为属性的接口声明类型参数T 的约束并使用它。
    • 如果您知道要对c.SomeParam 进行排序,则不需要表达式。您需要将"SomeParam" 作为字符串传递。
    • @OfirWinegarten 如果没有名为“ClientId”的公共属性或字段,它也会失败,但这不在 OP 问题的范围内。
    【解决方案2】:

    这很复杂:

    private static readonly MethodInfo OrderByDescending = (from x in typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                                            where x.Name == "OrderByDescending"
                                                            let args = x.GetGenericArguments()
                                                            where args.Length == 2
                                                            let pars = x.GetParameters()
                                                            where pars.Length == 2 &&
                                                                pars[0].ParameterType == typeof(IQueryable<>).MakeGenericType(args[0]) &&
                                                                pars[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(args))
                                                            select x).Single();
    
    public static IQueryable<T> OrderByStringDescending<T>(this IQueryable<T> query, string parameter)
    {
        var par = Expression.Parameter(typeof(T), "obj");
        var selector = Expression.PropertyOrField(par, parameter);
        var lambda = Expression.Lambda(selector, par);
        return (IQueryable<T>)OrderByDescending.MakeGenericMethod(typeof(T), selector.Type).Invoke(null, new object[] { query, lambda });
    }
    

    你必须建立一个Expression.Lambda。问题是在编译时不知道TKey 类型的情况下调用Queryable.OrderByDescending(有两个 参数,IQueryable&lt;&gt; 查询和Expression&lt;&gt;)很复杂。我通过反思来解决它。

    【讨论】:

      【解决方案3】:

      如果您只需要将表达式传递给对集合执行其他操作的某个方法,那么这很容易:

      public void Test<T,TKey>(IQueryable<T> collection, Expression<Func<T,TKey>> orderByExpr)
      {
          // your logic here 
          // ...
          collection = collection.OrderByDescending(orderByExpr);
      }
      

      你这样称呼它:

      this.Test(collection, c => c.ClientId);
      

      【讨论】:

        猜你喜欢
        • 2015-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-03
        • 1970-01-01
        • 1970-01-01
        • 2020-04-12
        • 2018-12-30
        相关资源
        最近更新 更多