如何在 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 你的意思是我应该定义一个类来包含这些值并覆盖它的 Equals 和 GetHashCode 方法吗?谢谢! @mjwills 我的猜测是,当 C# 对 相同的 ReadOnlyCollectionc# 中的对象通常通过reference 进行比较,而不是value。
这意味着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 时,您通常可以指定一个自定义“比较器”,它会按照您想要的方式处理比较。然后,收集方法在需要比较两个元素时使用此“比较器”。
适用于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();
您可以在 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?的主要内容,如果未能解决你的问题,请参考以下文章