【问题标题】:How to filter results based on selected results in a LINQ query?如何根据 LINQ 查询中的选定结果过滤结果?
【发布时间】:2011-05-02 17:06:33
【问题描述】:

我有一个 Foo 列表,我想根据 foo.HasBar 进行过滤。
Foo 也有一个属性 Baz。
选择 Foo 时,应过滤具有相同 Baz 对象的所有 Foo。
是否可以使用 LINQ 查询来实现这一点,还是应该使用 foreach 来代替?

编辑:这是一个示例数据集:

Foo.HasBar = true; Foo.Baz = 1;
Foo.HasBar = true; Foo.Baz = 1;
Foo.HasBar = false; Foo.Baz = 1;
Foo.HasBar = true; Foo.Baz = 2;
Foo.HasBar = false; Foo.Baz = 2;

我想要实现的是,如果 Foo.HasBar = true; Foo.Baz = 2; 已被选中,则不会对另一个 Foo.HasBar = true; Foo.Baz = 1; 执行其他迭代,或者不会对 Foo.HasBar = false; Foo.Baz = 2; 执行其他迭代。

下面是我使用 foreach 循环的方法:

var selectedFoos = new List<Foo>();

foreach(var foo in foos)
{
  if (selectedFoos.Exists(f => f.Baz == foo.Baz))
    continue;

  if (foo.HasBar)
     selectedFoos.Add(foo);
}

【问题讨论】:

  • 您要删除在另一个 Foo 中存在 Baz 的 Foo,还是要选择不同的 Baze?
  • 你只想要 foo.HasBar 的真值 RT?
  • @anishMarokey:是的,但是 HasBar 是性能密集型的,所以我正在尝试优化。这就是为什么 distinct 不适合我的情况。

标签: c# .net linq


【解决方案1】:

使用IEnumerable&lt;Foo&gt;.Distinct,并以高效的方式实现等于运算符,其中检查Baz 属性,如果Baz 不相等,则忽略HasBar 属性。您可以使用&amp;&amp; 执行此操作,因为如果左侧表达式为假,则不会计算右侧表达式。

然后,基于HasBar 和IEnumerable&lt;Foo&gt;.Where 进行过滤。

如果您不想让Foo 对象与Equals 运算符混淆,或者您需要针对不同情况使用不同的Equals 实现,那么请实现单独的IEqualityComparer&lt;Foo&gt;。

这还有一个好处是您可以避免在获得不同值的同时完全检查HasBar 属性。如果您跳过类本身的检查,它可能会导致细微的错误,因为人们可能希望它们是平等的。但是对于命名良好的自定义比较器,人们不太可能认为它会确保绝对平等。

下面是一些示例代码:

IEnumerable<Foo> selectedFoos =
    sampleDataSet
        .Distinct(new PerformantFooComparer())
        .Where(f => f.HasBar);

// ...

private class PerformantFooComparer : IEqualityComparer<Foo>
{
    public bool Equals(Foo x, Foo y)
    {
        bool isXNull = x == null;
        bool isYNull = y == null;

        return isXNull == isYNull
            && isXNull
            || (
                x.Baz == y.Baz
                // && x.HasBar == y.HasBar
                // HasBar commented out to avoid performance overhead.
                // It is handled by a Where(foo => foo.HasBar) filter
                );
    }

    public int GetHashCode(Foo obj)
    {
        if (obj == null)
            return 0;

        // See: http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
        int hash = 17;
        hash = hash * 23 + obj.Baz.GetHashCode();
        // HasBar intentionally not hashed
        return hash;
    }
}

【讨论】:

  • 好一个。顺便说一句,如果你只对 Baz 进行哈希运算,则不需要素数数学。
  • 如果您使用此解决方案,请确保在 PerformantFooComparer 类中添加三斜线注释,让人们知道它忽略了 HasBar 属性。
  • @Stilgar:是的,绝对正确。在这种情况下,您可以简单地返回 obj.Baz.GetHashCode()。不过,我假设他正在强烈修改他的数据类型,所以我想确保他可以将示例扩展到多个属性。
  • 格雷厄姆:你猜对了,我学到的东西比我所寻求的要多。我对你表示感谢:)
【解决方案2】:
var results = from f in Foos where (foo.HasBar) && (foo.Baz equals SelectedBaz) select f;

【讨论】:

    【解决方案3】:
    var q = from f in foos
            group f by f.Baz into g
            let tempFoo = g.FirstOrDefault(foo => foo.HasBar)
            where tempFoo != null
            select tempFoo;
    

    我能想到的最好的(目前)。

    使用 let 应该避免多次调用 FirstOrDefault ,因此假设 FirstOrDefault 的实现在找到结果后不会迭代,那么您的性能密集型 HasBar 不会被调用超过所需的次数。如果 let 没有使用 FirstOrDefault 应该在 where 子句中使用。

    【讨论】:

      【解决方案4】:

      你可以简单地去

      var filtered = from f in foos 
                     where foo.HasBar == true
                     select f;
      

      【讨论】:

      • 但是 HasBar 是一个真正的性能消费者。我正在尝试优化这里。
      • @the_drow if (selectedFoos.Exists(f => f.Baz == foo.Baz)) continue; ?如果这不存在,它也会过滤 RT?
      • 如果查询已经选择了具有 baz 的 foo,则无需选择具有相同 baz 的其他 foo。因此它应该跳过它。
      【解决方案5】:

      从您的代码中,您只需:

      foos.Where(f => !foos.Where(f2 => f2.HasBar).Any(s => s.Baz == f.Baz));
      

      【讨论】:

      • 但在创建结果时我还没有 selectedFoos。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-29
      • 2013-02-20
      • 1970-01-01
      • 2019-03-30
      • 1970-01-01
      • 1970-01-01
      • 2022-10-14
      相关资源
      最近更新 更多