【问题标题】:How to use ExceptWith with the type of HashSet<ReadOnlyCollection<string>> in C#?如何在 C# 中使用带有 HashSet<ReadOnlyCollection<string>> 类型的 exceptWith?
【发布时间】:2020-06-28 00:43:02
【问题描述】:
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]},但它给了我原来的测试1.
如何解决这个问题?或者有没有其他方法可以做同样的事情?谢谢!

【问题讨论】:

  • 简短回答:您可以为您的收藏定义一个自定义的 EqualityComparer。目前,它们通过实例(作为对象)进行比较,每个新的 HashSet 或 List 都是不同的,即使它们包含相同的元素。
  • 你明白它为什么会这样吗?
  • @Pac0 你的意思是我应该定义一个类来包含这些值并覆盖它的 EqualsGetHashCode 方法吗?谢谢!
  • @mjwills 我的猜测是,当 C# 对 相同的 ReadOnlyCollection 进行哈希处理时,例如 [1,2],它会返回 不同的哈希值对于价值相同的收藏,但我不知道如何解决这个问题,谢谢您的回复!
  • 如果这是您真正想要使用的数据,请将其改为 Tuple

标签: c# hashset readonly-collection


【解决方案1】:

c# 中的对象通常通过reference 进行比较,而不是value。 这意味着new object() != new object()。同理,new List&lt;int&gt;() { 1 } != new List&lt;int&gt;() { 1 }。另一方面,结构体和原语通过进行比较,而不是通过引用进行比较。

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

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

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

适用于ReadOnlyCollection&lt;T&gt; 的非常简单的比较器可能如下所示:

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();

您可以在 fiddle 中测试整个事情。

【讨论】:

  • 非常感谢您的详细解释和示例!
【解决方案2】:

这里有 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());

【讨论】:

  • 感谢源代码和例子的链接!现在我可以理解为什么它不起作用了!
猜你喜欢
  • 2013-03-05
  • 2011-12-29
  • 1970-01-01
  • 1970-01-01
  • 2021-05-22
  • 2019-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多