c# - 使用表达式树创建 DistinctBy

标签 c# linq expression-trees

我想创建一个扩展 IQueryable 的方法,用户可以在其中在字符串中指定一个属性名称,他希望通过该名称来区分集合。我想使用带有 HashSet 的逻辑。 我基本上想模拟这段代码:

HashSet<TResult> set = new HashSet<TResult>();

foreach(var item in source)
{
    var selectedValue = selector(item);

    if (set.Add(selectedValue))
        yield return item;
}

使用表达式树。

这是我到目前为止的进展:

private Expression AssembleDistinctBlockExpression (IQueryable queryable, string propertyName)
    {
        var propInfo = queryable.ElementType.GetProperty(propertyName);
        if ( propInfo == null )
            throw new ArgumentException();

        var loopVar = Expression.Parameter(queryable.ElementType, "");
        var selectedValue = Expression.Variable(propInfo.PropertyType, "selectedValue");

        var returnListType = typeof(List<>).MakeGenericType(queryable.ElementType);
        var returnListVar = Expression.Variable(returnListType, "return");
        var returnListAssign = Expression.Assign(returnListVar, Expression.Constant(Activator.CreateInstance(typeof(List<>).MakeGenericType(queryable.ElementType))));
        var hashSetType = typeof(HashSet<>).MakeGenericType(propInfo.PropertyType);
        var hashSetVar = Expression.Variable(hashSetType, "set");
        var hashSetAssign = Expression.Assign(hashSetVar, Expression.Constant(Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(propInfo.PropertyType))));

        var enumeratorVar = Expression.Variable(typeof(IEnumerator<>).MakeGenericType(queryable.ElementType), "enumerator");
        var getEnumeratorCall = Expression.Call(queryable.Expression, queryable.GetType().GetTypeInfo().GetDeclaredMethod("GetEnumerator"));
        var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall);

        var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext"));

        var breakLabel = Expression.Label("loopBreak");

        var loopBlock = Expression.Block(
            new [] { enumeratorVar, hashSetVar, returnListVar },
            enumeratorAssign,
            returnListAssign,
            hashSetAssign,
            Expression.TryFinally(
                Expression.Block(
                    Expression.Loop(
                        Expression.IfThenElse(
                        Expression.Equal(moveNextCall, Expression.Constant(true)),
                        Expression.Block(
                            new[] { loopVar },
                            Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")),
                            Expression.Assign(selectedValue, Expression.MakeMemberAccess(loopVar, propInfo)),
                            Expression.IfThen(
                                Expression.Call(typeof(HashSet<>), "Add", new Type[] { propInfo.PropertyType }, hashSetVar, selectedValue),
                                Expression.Call(typeof(List<>), "Add", new Type[] { queryable.ElementType }, returnListVar, loopVar)
                                )
                            ),
                        Expression.Break(breakLabel)
                        ),
                    breakLabel
                    ),
                    Expression.Return(breakLabel, returnListVar)
                ),
                Expression.Block(
                    Expression.Call(enumeratorVar, typeof(IDisposable).GetMethod("Dispose"))
                )
            )
        );
        return loopBlock;
    }

当为变量 loopBlock 调用 Expression.Block 时出现异常,如下所示:

No method 'Add' exists on type 'System.Collections.Generic.HashSet`1[T]'.

最佳答案

Expression.Call method overload您正在使用的是静态方法。

引用上面的引用资料:

Creates a MethodCallExpression that represents a call to a static (Shared in Visual Basic) method by calling the appropriate factory method.

你需要做的是使用an overload of that method that is for calling instance methods .

代码的相关部分如下所示:

Expression.IfThen(
    Expression.Call(hashSetVar, "Add", new Type[] { }, selectedValue),
    Expression.Call(returnListVar, "Add", new Type[] { }, loopVar))

注意我们现在如何传递我们需要在 Expression.Call 的第一个参数中调用的实例(表达式) .

另请注意,我们传递了一个空类型参数列表。原因是 Add此类中的方法没有任何类型参数。类型参数 THashSet<T>List<T>是在类级别定义的,而不是在方法级别定义的。

只有当它们像这样在方法本身上定义时,您才需要指定类型参数:

void SomeMethod<T1>(...

关于c# - 使用表达式树创建 DistinctBy,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37504370/

相关文章:

c# - 使用 LINQ 反序列化 Json 的更优雅的方法?

c# - Expression.Call 在简单的 lambda 表达式中。可能吗?

c# - 字段的 ASP.NET MVC 内联帮助程序

c# - LINQ to Entities 仅支持使用 IEntity 接口(interface)转换 EDM 原语或枚举类型

c# - 如何在 jquery 变量中获取业务类属性值

c# - IProgress<T> 同步

c# - LINQ 按总和值分组

c# - 如何让 EF6 生成高效的 in(...) 查询

c# - 状态设计模式用户界面

c# - await ExecuteNonQueryAsync() 仍然阻塞我的 UI