如何在 C# 中使用带有 HashSet<ReadOnlyCollection<string>> 类型的 exceptWith?

Posted

技术标签:

【中文标题】如何在 C# 中使用带有 HashSet<ReadOnlyCollection<string>> 类型的 exceptWith?【英文标题】:How to use ExceptWith with the type of HashSet<ReadOnlyCollection<string>> in C#? 【发布时间】: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 【参考方案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());

【讨论】:

感谢源代码和例子的链接!现在我可以理解为什么它不起作用了!

以上是关于如何在 C# 中使用带有 HashSet<ReadOnlyCollection<string>> 类型的 exceptWith?的主要内容,如果未能解决你的问题,请参考以下文章

C# HashSet集合类型使用介绍

是否有像 C++ 中那样带有分隔符的 C# 原始字符串?

怎样在C#中把0-10这11个数随机顺序不重复的放到集合里面?

C# HashSet<T> 只读解决方法

C#泛型集合之——哈希集合

如何通过构造初始化HashSet值?