Для поиска HashSet.CreateSetComparer() не может указать IEqalityComparer, есть ли альтернатива?
В внутреннем источнике есть такой конструктор public HashSetEqualityComparer(IEqualityComparer<T> comparer)
, но он внутренний, поэтому я не могу его использовать.
По умолчанию, HashSet<T>.CreateSetComparer()
просто использует конструктор без параметров, который будет применять EqualityComparer<T>.Default
.
HashSetEqualityComparer<T>
с выбором IEqualityComparer<T>
, не копируя код из исходного кода?3 ответа:
Я думаю, что лучшее решение-это использование
SetEquals
. Он выполняет необходимую вам работу и точно так же, как это делаетHashSetEqualityComparer
, но он будет учитывать любые пользовательские компараторы, определенные в множествах его сравнения.Итак, в вашем конкретном сценарии, где вы хотите использовать
HashSet<T>
в качестве ключа словаря, вам нужно реализоватьIEqualityComparer<HashSet<T>>
, который используетSetEquals
и "заимствует" источник ссылки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
, который использует пользовательские компараторы, но это неудачное поведение связано, IMHO, больше с ошибкой существующей реализации, чем с отсутствием необходимой перегрузки; нет никаких причин, почемуCreateSetComparer()
не может вернутьIEqualityComparer
, который фактически использует компараторы множеств, которые его сравнивают, как показывает приведенный выше код.Если бы у меня был голос в нем,
CreateSetComparer()
вообще не был бы статическим методом. Тогда было бы очевидно или, по крайней мере, предсказуемо, что любой возвращаемый компаратор будет создан с помощью компаратор текущего набора.
Я согласен @InBetween, использование
SetEquals
является лучшим способом. Даже если добавить конструктор все равно не получится добиться того, чего вы хотите.Пожалуйста, смотрите этот код: http://referencesource.microsoft.com/#System.Core/System/Collections/Generic/HashSet.cs,1360
Вот что я пытаюсь сделать:
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); } }
Избегайте этого класса, если вы используете пользовательские компараторы. Он использует свой собственный компаратор проверки на равенство для выполнения метода GetHashCode, но при выполнении равенства(Набор1, Набор2) если Набор1 и Набор2 же компаратор проверки на равенство, на HashSetEqualityComparer будет использовать компаратор наборов. HashsetEqualityComparer будет использовать свой собственный компаратор только для равных, если Set1 и Set2 имеют разные компараторы
Становится все хуже. Это называется HashSet.HashSetEquals, в котором есть ошибка (см. https://referencesource.microsoft.com/#system.core/System/Collections/Generic/HashSet.cs строка 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(); } }
Что касается других ответов на этот вопрос (у меня нет представителя, чтобы комментировать):
Вильгельм Ляо: его ответ также содержит ошибку, потому что он скопирован из справочного источника
- между ними: решение не симметрично. CustomHashSetEqualityComparer.Equals (A, B) не всегда равно CustomHashSetEqualityComparer.Равняется(B, A). Я бы этого испугался.
Я думаю, что надежная реализация должна выдавать исключение, если она сталкивается с набором, который имеет другой компаратор к своему собственному. Онможет всегда использовать свой собственный компаратор и игнорировать установленный компаратор, но это даст странное и неинтуитивное поведение.