(如何)我可以创建一个带有类型参数作为变量的选择器函数吗?

(How) can I create a selector function with a type parameter as a variable?

方法 System.Linq.Queryable.OrderBy.OrderByDescending.ThenBy.ThenByDescending 有一个带有两个类型参数的参数“keySelector”:TSource 和 TKey。

public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);

我想预先创建keySelector参数,然后将其传递给上述四个方法。

假设我有一个具有三个属性的实体 class MyEntity,例如一个字符串,一个整数和一个布尔值(以及一些继承 MyEntity 的实体类型)。我想通过另一种方法为排序方法创建 keySelector 参数。

private static Expression<Func<TSource, TKey>> GetExpression<TSource, TKey>(string propertyName) where TSource : MyEntity
{
    return propertyName switch
    {
        "propa" => entity => entity.MyPropertyA,
        "propb" => entity => entity.MyPropertyB,
        "propc" => entity => entity.MyPropertyC,
        _ => throw new Exception("Unknown property: " + propertyName)
    }
}

现在我遇到了两个错误:

CS1662 可以通过 returning entity => (TKey)entity.MyPropertyA 解决,但 CS0029 逻辑上仍然存在。

有什么办法可以提前建立keySelector参数吗?因为目前我被迫重复那个 switch 表达式四次。 (实际上有九个属性。)

从不同的角度解决问题,因为问题标题中所要求的内容并不是真正可行的:

Is there any way to build the keySelector parameter in advance? Because currently I am forced to repeat that switch expression four times. (And there are actually nine properties.)

您可以通过以下方式减少重复:a) 首先应用一个愚蠢的 OrderBy 表达式,以便您始终使用 IOrderedQueryable<T> 和 b) 提供您自己的扩展方法来决定哪个 ThenXxx 基于布尔参数链接的方法1:

public static IOrderedQueryable<TSource> ThenBy<TSource, TKey>(
                      this IOrderedQueryable<TSource> source,
                      Expression<Func<TSource, TKey>> keySelector,
                      bool descending = false)
{
    if(descending) return source.ThenByDescending(keySelector);
    return source.ThenBy(keySelector);
}

那么在你的调用代码中,你有

var myUnorderedQuery = ...
var mySortedQuery = myUnsortedQuery.OrderBy(x=>1);
foreach(<sort item to apply>)
{
    bool descending = <logic>
    switch (propertyName)
    {
        case "propa":
            mySortedQuery = mySortedQuery.ThenBy(entity=>entity.PropertyA, descending);
            break;
        ...
    }
}

现在你的代码中只出现了 switch 一次。


如果这是 IEnumerable<T>,这会更容易,因为上述扩展方法的道德等价物是 built-in 作为 CreatedOrderedEnumerable,尽管在那种情况下你必须提供比较器或知道默认为 Comparer<T>.Default.


1另一种方法是接受 IQueryable<T>OrderBy 扩展方法,检查它是否实际上是 IOrderedQueryable<T> 并将其用于 select 是否使用 OrderByThenBy。但是我发现这种方法有点“神奇”,如果你想覆盖已经证明是 IOrderedQueryable<T>.

的东西的顺序,那会很痛苦。

如果使用object作为常用键类型(SQL翻译时不会使用该类型进行排序)并显式指定TSource类型,则可以:

public static Expression<Func<MyEntity, Object>> GetExpression(string propName) {
    switch (propName) {
        case "propa":
            return (MyEntity s) => s.MyPropertyA;

        case "propb":
            return (MyEntity s) => s.MyPropertyB;

        case "propc":
            return (MyEntity s) => s.MyPropertyC;

        default:
            throw new Exception($"Unrecognized property: {propName}");
    }
}