在internal source中有一个构造函数public HashSetEqualityComparer(IEqualityComparer<T> comparer)
,但它是内部的,所以我无法使用它。
默认情况下,HashSet<T>.CreateSetComparer()
只使用无参数构造函数,该构造函数将应用EqualityComparer<T>.Default
。
有没有办法让HashSetEqualityComparer<T>
选择IEqualityComparer<T>
,而不从源代码中复制代码?
答案 0 :(得分:3)
我认为最佳解决方案是使用SetEquals
。它完成了您所需要的工作,其工作方式与HashSetEqualityComparer
完全相同,但将考虑其比较中定义的任何自定义比较器。
因此,在您希望使用HashSet<T>
作为字典键的特定场景中,您需要实现一个使用IEqualityComparer<HashSet<T>>
和&#34的SetEquals
;借用&#34; HashSetEqualityComparer.GetHashCode()
的参考来源:
public class CustomHashSetEqualityComparer<T>
: IEqualityComparer<HashSet<T>>
{
public bool Equals(HashSet<T> x, HashSet<T> y)
{
if (ReferenceEquals(x, null))
return false;
return x.SetEquals(y);
}
public int GetHashCode(HashSet<T> set)
{
int hashCode = 0;
if (set != null)
{
foreach (T t in set)
{
hashCode = hashCode ^
(set.Comparer.GetHashCode(t) & 0x7FFFFFFF);
}
}
return hashCode;
}
}
但是,是的,这是一个小小的痛苦,没有办法直接创建一个利用自定义比较器的SetEqualityComparer
,但这种不幸的行为是由于,恕我直言,更多的是现有实施的错误,而不是缺乏需要超载;没有理由CreateSetComparer()
无法返回IEqualityComparer
实际使用其比较的集合的比较器,如上面的代码所示。
如果我有声音,CreateSetComparer()
根本不是静态方法。然后很明显,或者至少可以预测,无论返回什么比较器都将使用当前设置的比较器创建。
答案 1 :(得分:1)
我同意@InBetween,使用SetEquals
是最好的方法。即使添加构造函数仍然无法实现你想要的。
我试着这样做:
class HashSetEqualityComparerWrapper<T> : IEqualityComparer<HashSet<T>>
{
static private Type HashSetEqualityComparerType = HashSet<T>.CreateSetComparer().GetType();
private IEqualityComparer<HashSet<T>> _comparer;
public HashSetEqualityComparerWrapper()
{
_comparer = HashSet<T>.CreateSetComparer();
}
public HashSetEqualityComparerWrapper(IEqualityComparer<T> comparer)
{
_comparer = HashSet<T>.CreateSetComparer();
if (comparer != null)
{
FieldInfo m_comparer_field = HashSetEqualityComparerType.GetField("m_comparer", BindingFlags.NonPublic | BindingFlags.Instance);
m_comparer_field.SetValue(_comparer, comparer);
}
}
public bool Equals(HashSet<T> x, HashSet<T> y)
{
return _comparer.Equals(x, y);
}
public int GetHashCode(HashSet<T> obj)
{
return _comparer.GetHashCode(obj);
}
}
我花了5分钟来实现另一个版本的HashSetEqualityComparer<T>
源代码。并重写bool Equals(HashSet<T> x, HashSet<T> y)
方法。这并不复杂。所有代码都只是从源代码复制和粘贴,我只是修改了一下。
class CustomHashSetEqualityComparer<T> : IEqualityComparer<HashSet<T>>
{
private IEqualityComparer<T> m_comparer;
public CustomHashSetEqualityComparer()
{
m_comparer = EqualityComparer<T>.Default;
}
public CustomHashSetEqualityComparer(IEqualityComparer<T> comparer)
{
if (comparer == null)
{
m_comparer = EqualityComparer<T>.Default;
}
else
{
m_comparer = comparer;
}
}
// using m_comparer to keep equals properties in tact; don't want to choose one of the comparers
public bool Equals(HashSet<T> x, HashSet<T> y)
{
// http://referencesource.microsoft.com/#System.Core/System/Collections/Generic/HashSet.cs,1360
// handle null cases first
if (x == null)
{
return (y == null);
}
else if (y == null)
{
// set1 != null
return false;
}
// all comparers are the same; this is faster
if (AreEqualityComparersEqual(x, y))
{
if (x.Count != y.Count)
{
return false;
}
}
// n^2 search because items are hashed according to their respective ECs
foreach (T set2Item in y)
{
bool found = false;
foreach (T set1Item in x)
{
if (m_comparer.Equals(set2Item, set1Item))
{
found = true;
break;
}
}
if (!found)
{
return false;
}
}
return true;
}
public int GetHashCode(HashSet<T> obj)
{
int hashCode = 0;
if (obj != null)
{
foreach (T t in obj)
{
hashCode = hashCode ^ (m_comparer.GetHashCode(t) & 0x7FFFFFFF);
}
} // else returns hashcode of 0 for null hashsets
return hashCode;
}
// Equals method for the comparer itself.
public override bool Equals(Object obj)
{
CustomHashSetEqualityComparer<T> comparer = obj as CustomHashSetEqualityComparer<T>;
if (comparer == null)
{
return false;
}
return (this.m_comparer == comparer.m_comparer);
}
public override int GetHashCode()
{
return m_comparer.GetHashCode();
}
static private bool AreEqualityComparersEqual(HashSet<T> set1, HashSet<T> set2)
{
return set1.Comparer.Equals(set2.Comparer);
}
}
答案 2 :(得分:1)
如果使用自定义比较器,请避免此类。它使用自己的相等比较器执行GetHashCode,但是在执行Equals(Set1,Set2)时,如果Set1和Set2具有相同的相等比较器,则HashSetEqualityComparer将使用集合的比较器。如果Set1和Set2具有不同的比较器,则HashsetEqualityComparer仅将其自己的比较器用于等于
情况变得更糟。它调用了HashSet.HashSetEquals,其中包含一个错误(请参阅this行1489,该行在执行子集检查之前缺少if (set1.Count != set2.Count) return false
。
该错误由以下程序说明:
class Program
{
private class MyEqualityComparer : EqualityComparer<int>
{
public override bool Equals(int x, int y)
{
return x == y;
}
public override int GetHashCode(int obj)
{
return obj.GetHashCode();
}
}
static void Main(string[] args)
{
var comparer = HashSet<int>.CreateSetComparer();
var set1 = new HashSet<int>(new MyEqualityComparer()) { 1 };
var set2 = new HashSet<int> { 1, 2 };
Console.WriteLine(comparer.Equals(set1, set2));
Console.WriteLine(comparer.Equals(set2, set1)); //True!
Console.ReadKey();
}
}
关于此问题的其他答案(我没有代表要评论):
我认为,如果健壮的实现遇到比较器与其本身不同的集合,则应抛出异常。它可以始终使用自己的比较器,而忽略设置的比较器,但这会带来奇怪且不直观的行为。