C# Linq intersect/except如何针对对象的一部分进行操作?

26

我有一个类:

class ThisClass
{
  private string a {get; set;}
  private string b {get; set;}
}

我想使用Linq的交集(Intersect)和差集(Except)方法,即:

private List<ThisClass> foo = new List<ThisClass>();
private List<ThisClass> bar = new List<ThisClass>();

然后我分别填充这两个列表。举个例子(我知道以下不是正确的代码,只是伪代码),我想要实现以下功能:

foo[a].Intersect(bar[a]);

我该如何做到这一点?


1
你想要什么?用语言解释一下,你想从这行代码 foo[a].Intersect(bar[a]); 中得到什么。 - Nikhil Agrawal
7个回答

41

如果你想要一个属性的列表来做交集,那么所有其他漂亮的 LINQ 解决方案都可以正常工作。 但是!如果你想对整个类进行交集操作,结果得到的会是 List<ThisClass> 而不是 List<string>,那么你就需要编写自己的相等比较器。

foo.Intersect(bar, new YourEqualityComparer());

Except一样。

public class YourEqualityComparer: IEqualityComparer<ThisClass>
{

    #region IEqualityComparer<ThisClass> Members


    public bool Equals(ThisClass x, ThisClass y)
    {
        //no null check here, you might want to do that, or correct that to compare just one part of your object
        return x.a == y.a && x.b == y.b;
    }


    public int GetHashCode(ThisClass obj)
    {
        unchecked
        {
            var hash = 17;
                            //same here, if you only want to get a hashcode on a, remove the line with b
            hash = hash * 23 + obj.a.GetHashCode();
            hash = hash * 23 + obj.b.GetHashCode();

            return hash;    
        }
    }

    #endregion
}

这正是我一直在寻找的。两个250K列表在几秒钟内进行了比较。谢谢! - Jakub Sluka

38
也许。
// returns list of intersecting property 'a' values
foo.Select(f => f.a).Intersect(bar.Select(b => b.a));

顺便提一下,属性a应该是公共的。


6
此处返回一个 Property 集合。我在寻找返回 Object 集合的内容。请参见 Patryk 的下方评论。 - Jakub Sluka

8

不确定与intersect和compare相比的速度,但是考虑如下方法:

//Intersect
var inter = foo.Where(f => bar.Any(b => b.a == f.a));
//Except - values of foo not in bar
var except = foo.Where(f => !bar.Any(b => b.a == f.a));

5
这是一个O(n * m)的算法,而IntersectExcept都是O(n + m)。这使得它相当糟糕。它还要多次迭代bar,这在各种情况下可能是一个重大问题(每次迭代可能不会产生相同的结果,可能会查询数据库或执行昂贵的计算,可能在迭代时造成副作用等)。 - Servy
@Servy答案的扩展:只需查看源代码来评估Intersect / Except方法的复杂性。 - vladimir

2
foo.Select(x=>x.a).Intersect(bar.Select(x=>x.a))

0
你到底想要什么效果?是想要一个由所有类中的 a 组成的字符串列表,还是想要一个由两个具有相同 a 值的 ThisClass 实例组成的列表?
如果是前者,@lazyberezovksy 和 @Tilak 给出的两个答案应该可以解决问题。如果是后者,你需要重写 IEqualityComparer<ThisClass> 或者 IEquatable<ThisClass> 接口,以便 Intersect 方法知道如何判断两个 ThisClass 实例是否相等。
 private class ThisClass : IEquatable<ThisClass>
 {
     private string a;

     public bool Equals(ThisClass other)
     {
        return string.Equals(this.a, other.a);
     }
 }

然后你可以直接调用:

 var intersection = foo.Intersect(bar);     

1
当实现IEquatable时,您总是需要重写GetHashCode。如果不这样做,它将无法正常工作。 - Servy

0

虽然这篇文章是很旧的了,但你也可以不覆盖Equals和GetHashCode方法,而是直接在类本身上重写它们。

class ThisClass
{
  public string a {get; set;}
  private string b {get; set;}

  public override bool Equals(object obj)
  {
    // If you only want to compare on a
    ThisClass that = (ThisClass)obj;
    return string.Equals(a, that.a/* optional: not case sensitive? */);
  }

  public override int GetHashCode()
  {
    return a.GetHashCode();
  }
}

-3

你应该创建IEqualityComparer。你可以将IEqualityComparer传递给Intersect()方法。这将帮助你更容易地获取与bar相交的List。

var intersectionList = foo.Intersect(bar, new ThisClassEqualityComparer()).ToList();


class ThisClassEqualityComparer : IEqualityComparer<ThisClass>
{

    public bool Equals(ThisClass b1, ThisClass b2)
    {
        return b1.a == b2.a;
    }


    public int GetHashCode(Box bx)
    {
       // To ignore to compare hashcode, please consider this.
       // I would like to force Equals() to be called
       return 0;
    }

}

1
你不应该像这样从哈希码中返回 0。那会完全影响性能。相反,你应该使用 a 的哈希码。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接