在动态类型上使用 IEqualityComparer 调用 Linq GroupBy

Call Linq GroupBy with IEqualityComparer on dynamic type

我想创建一个忽略大小写的动态 GroupBy 实现。我正在使用 Expression.Call,它允许我将表达式作为参数传递。

关于如何创建自定义比较器有几个答案,但这个问题是关于如何动态传递比较器的。

完整方法如下:

public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values)
{
    if (source == null) throw new ArgumentNullException("source");
    if (keySelector == null) throw new ArgumentNullException("keySelector");
    if (elementSelector == null) throw new ArgumentNullException("elementSelector");
    LambdaExpression keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, false, values);
    LambdaExpression elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, false, values);

    return source.Provider.CreateQuery(
        Expression.Call(
            typeof(Queryable), 
            "GroupBy", 
            new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type },
            source.Expression, 
            Expression.Quote(keyLambda), 
            Expression.Quote(elementLambda)
        )
    );
}

Queryable.GroupBy 的调用由以下人员创建:

Expression.Call(typeof(Queryable), "GroupBy", 
  new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type },
  source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda))

Queryable.GroupBy 允许传递自定义 IEqualityComparer。我怎样才能做到这一点? Expression.Call 只允许我传递 Expression.

类型的参数

有没有其他方法可以忽略大小写进行分组,例如动态覆盖键的 GetHashCode()?

您应该以相同的方式调用它,就像通常在 GroupBy 调用中添加比较器一样:

return source.Provider.CreateQuery(
    Expression.Call(
        typeof(Queryable),
        "GroupBy", 
        new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type },
        source.Expression,
        Expression.Quote(keyLambda),
        Expression.Quote(elementLambda),
        Expression.Constant(StringComparer.InvariantCultureIgnoreCase)
    )
);
这里不能使用

StringComparer,因为类型是动态的,不是字符串。我必须详细说明 Krzysztofs 的回答才能找到有效的解决方案。

首先创建与 keyLambda.Body.Type 类型相同的自定义动态比较器实例 DynamicCaseInsensitiveComparer<T>(实现 IEqualityComparer)。由于类型是由变量提供的,因此您必须使用 MakeGenericType。然后在GroupBy调用中添加:

var comparerType = typeof(DynamicCaseInsensitiveComparer<>).MakeGenericType(keyLambda.Body.Type);
var keyComparer = Activator.CreateInstance(comparerType);

return source.Provider.CreateQuery(
    Expression.Call(
        typeof(Queryable),
        "GroupBy", 
        new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type },
        source.Expression,
        Expression.Quote(keyLambda),
        Expression.Quote(elementLambda),
        Expression.Constant(keyComparer)
    )
);

How to create a custom comparer 已在其他问题中得到解答,例如参见IEqualityComparer for Annoymous Type