0
HashSet<ReadOnlyCollection<int>> test1 = new HashSet<ReadOnlyCollection<int>> ();
for (int i = 0; i < 10; i++) {
    List<int> temp = new List<int> ();
    for (int j = 1; j < 2; j++) {
        temp.Add (i);
        temp.Add (j);
    }
    test1.Add (temp.AsReadOnly ());
}

这里 test1 是 {[0,1], [1,1], [2,1], [3,1], [4,1], [5,1], [6,1], [7,1 ], [8,1], [9,1]}

HashSet<ReadOnlyCollection<int>> test2 = new HashSet<ReadOnlyCollection<int>> ();
for (int i = 5; i < 10; i++) {
    List<int> temp = new List<int> ();
    for (int j = 1; j < 2; j++) {
        temp.Add (i);
        temp.Add (j);
    }
    test2.Add (temp.AsReadOnly ());
}

这里 test2 是 {[5,1], [6,1], [7,1], [8,1], [9,1]}

test1.ExceptWith(test2);

这样做之后,我希望 test1 是 {[0,1], [1,1], [2,1], [3,1], [4,1]},但它给了我原来的 test1。
如何解决这个问题?或者有没有其他方法可以做同样的事情?谢谢!

4

2 回答 2

2

c# 中的对象通常按引用进行比较,而不是按进行比较。这意味着new object() != new object(). 以同样的方式,new List<int>() { 1 } != new List<int>() { 1 }。另一方面,结构体和原语是按进行比较,而不是按引用进行比较。

一些对象会覆盖它们的相等方法来比较值。例如字符串:new string(new[] { 'a', 'b', 'c'}) == "abc",即使object.ReferenceEquals(new string(new[] { 'a', 'b', 'c'}), "abc") == false.

但是集合、列表、数组等没有。有充分的理由 - 在比较两个整数列表时,你想比较什么?确切的元素,无论顺序如何?按顺序排列的确切元素?元素的总和?没有一个答案适合所有事情。通常你可能真的想检查你是否有相同的对象。

使用集合或 LINQ 时,您通常可以指定一个自定义“比较器”,它将按照您想要的方式处理比较。然后,收集方法在需要比较两个元素时使用这个“比较器”。

一个非常简单的适用于 a 的比较器ReadOnlyCollection<T>可能如下所示:

class ROCollectionComparer<T> : IEqualityComparer<IReadOnlyCollection<T>>
{
    private readonly IEqualityComparer<T> elementComparer;

    public ROCollectionComparer() : this(EqualityComparer<T>.Default) {}
    public ROCollectionComparer(IEqualityComparer<T> elementComparer) {
        this.elementComparer = elementComparer;
    }

    public bool Equals(IReadOnlyCollection<T> x, IReadOnlyCollection<T> y)
    {
        if(x== null && y == null) return true;
        if(x == null || y == null) return false;
        if(object.ReferenceEquals(x, y)) return true;

        return x.Count == y.Count && 
            x.SequenceEqual(y, elementComparer);
    }

    public int GetHashCode(IReadOnlyCollection<T> obj)
    {       
        // simplistic implementation - but should OK-ish when just looking for equality
        return (obj.Count, obj.Count == 0 ? 0 : elementComparer.GetHashCode(obj.First())).GetHashCode();
    }
}

然后您可以比较默认相等检查的行为和您自定义的行为:

var std = new HashSet<int[]>(new[] { new[] { 1, 2 }, new[] { 2, 2}});
std.ExceptWith(new[] { new[] { 2, 2}});
std.Dump();

var custom = new HashSet<int[]>(new[] { new[] { 1, 2 }, new[] { 2, 2 } }, new ROCollectionComparer<int>());
custom.ExceptWith(new[] { new[] { 2, 2 }});
custom.ExceptWith(new[] { new int[] { }});
custom.Dump();

你可以在这个小提琴中测试整个事情。

于 2020-03-16T22:46:12.513 回答
0

在这里,您有 exceptWith 的实现:

https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System.Core/System/Collections/Generic/HashSet.cs#L532

它的实际作用是:

 // remove every element in other from this
 foreach (T element in other) {
    Remove(element);
 }

Remove实施:

https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System.Core/System/Collections/Generic/HashSet.cs#L287

 if (m_slots[i].hashCode == hashCode && m_comparer.Equals(m_slots[i].value, item)) {

所以如果 hashcode 不一样,Remove 什么都不做。

一个小测试证明hashcode不一样:

    List<int> temp = new List<int> ();
     temp.Add(1);
     temp.Add(2);

    HashSet<ReadOnlyCollection<int>> test1 = new HashSet<ReadOnlyCollection<int>> ();
    HashSet<ReadOnlyCollection<int>> test2 = new HashSet<ReadOnlyCollection<int>> ();
    test1.Add (temp.AsReadOnly ());
    test2.Add (temp.AsReadOnly ());

    Console.WriteLine(test1.First().GetHashCode() == test2.First().GetHashCode());
于 2020-03-16T22:40:25.607 回答