ImmutableHashSet .Contains возвращает false
У меня есть список (если быть точным ImmutableHashSet<ListItem>
из System.Collections.Immutable) базовых элементов и попробуйте вызвать следующий код
_baseList.Contains(derivedItem)
но это возвращает ложь.
Несмотря на то, что все следующие строки кода возвращают true
object.ReferenceEquals(_baseList.First(), derivedItem)
object.Equals(_baseList.First(), derivedItem)
_baseList.First().GetHashCode() == derivedItem.GetHashCode()
Я даже могу написать следующее, и он возвращает истину:
_baseList.OfType<DerivedClass>().Contains(derivedItem)
Что я делаю не так, я хотел бы избежать написания.OfType вещи.
Редактировать:
private ImmutableHashSet<BaseClass> _baseList;
public class BaseClass
{
}
public class DerivedClass : BaseClass
{
}
public void DoStuff()
{
var items = _baseList.OfType<DerivedClass>().ToList();
foreach (var derivedItem in items)
{
RemoveItem(derivedItem);
}
}
public void RemoveItem(BaseClass derivedItem)
{
if (_baseList.Contains(derivedItem))
{
//doesn't reach this place, since _baseList.Contains(derivedItem) returns false...
_baseList = _baseList.Remove(derivedItem);
}
//object.ReferenceEquals(_baseList.First(), derivedItem) == true
//object.Equals(_baseList.First(), derivedItem) == true
//_baseList.First().GetHashCode() == derivedItem.GetHashCode() == true
//_baseList.OfType<DerivedClass>().Contains(derivedItem) == true
}
Edit2:
Здесь воспроизводимый код моей проблемы, похоже ImmutableHashSet<>
кэши GetHashCode
и не сравнивает текущий GetHashCode
с записями внутри списка, есть ли способ сказать ImmutableHashSet<>
что GetHashCode
из предметов может быть другим, по крайней мере для предмета, который я в настоящее время проверяю, так как эй это чертовски та же ссылка...
namespace ConsoleApplication1
{
class Program
{
private static ImmutableHashSet<BaseClass> _baseList;
static void Main(string[] args)
{
_baseList = ImmutableHashSet.Create<BaseClass>();
_baseList = _baseList.Add(new DerivedClass("B1"));
_baseList = _baseList.Add(new DerivedClass("B2"));
_baseList = _baseList.Add(new DerivedClass("B3"));
_baseList = _baseList.Add(new DerivedClass("B4"));
_baseList = _baseList.Add(new DerivedClass("B5"));
DoStuff();
Console.WriteLine(_baseList.Count); //output is 5 - put it should be 0...
Console.ReadLine();
}
private static void DoStuff()
{
var items = _baseList.OfType<DerivedClass>().ToList();
foreach (var derivedItem in items)
{
derivedItem.BaseString += "Change...";
RemoveItem(derivedItem);
}
}
private static void RemoveItem(BaseClass derivedItem)
{
if (_baseList.Contains(derivedItem))
{
_baseList = _baseList.Remove(derivedItem);
}
}
}
public abstract class BaseClass
{
private string _baseString;
public string BaseString
{
get { return _baseString; }
set { _baseString = value; }
}
public BaseClass(string baseString)
{
_baseString = baseString;
}
public override int GetHashCode()
{
unchecked
{
int hashCode = (_baseString != null ? _baseString.GetHashCode() : 0);
return hashCode;
}
}
}
public class DerivedClass : BaseClass
{
public DerivedClass(string baseString)
: base(baseString)
{
}
}
}
Если бы я изменил ImmutableHashSet<>
в ImmutableList<>
код работает нормально, так что если вы, ребята, не придумаете какой-либо хорошей идеи, я переключусь на список.
2 ответа
Объекты, которые используются в словарях и других структурах данных, связанных с хешированием, должны иметь неизменную идентичность - все структуры данных, связанные с хешированием, предполагают, что после добавления объекта в словарь его хеш-код не изменится.
Этот код не будет работать:
private static void DoStuff()
{
var items = _baseList.OfType<DerivedClass>().ToList();
foreach (var derivedItem in items)
{
derivedItem.BaseString += "Change...";
RemoveItem(derivedItem);
}
}
private static void RemoveItem(BaseClass derivedItem)
{
if (_baseList.Contains(derivedItem))
{
_baseList = _baseList.Remove(derivedItem);
}
}
_baseList.Contains()
в RemoveItem()
как называется DoStuff()
будет возвращать false для каждого отдельного элемента, потому что вы изменили идентификатор сохраненного элемента - его BaseString
имущество.
Я думаю, что вы ответили на свой вопрос в своем редактировании. Вы не можете изменить хэш-код после добавления элемента в HashSet. Это нарушает договор о том, как работает HashSet.
Смотрите эту прекрасную статью Эрика Липперта для получения дополнительной информации по этой теме.
В частности, говорится следующее:
Рекомендация: целое число, возвращаемое GetHashCode, никогда не должно меняться
В идеале хеш-код изменяемого объекта должен вычисляться только из полей, которые не могут изменяться, и, следовательно, значение хеш-функции объекта остается неизменным на протяжении всего времени его существования.
Однако это только руководство по идеальной ситуации; фактическое правило:
Правило: целое число, возвращаемое GetHashCode, никогда не должно изменяться, пока объект содержится в структуре данных, которая зависит от стабильности хеш-кода
Разрешается, хотя и опасно, создавать объект, значение хеш-кода которого может изменяться по мере изменения полей объекта. Если у вас есть такой объект, и вы помещаете его в хеш-таблицу, то код, который мутирует объект, и код, который поддерживает хеш-таблицу, должны иметь некоторый согласованный протокол, который гарантирует, что объект не будет мутирован, пока он находится в хеш-таблица. Как выглядит этот протокол, зависит от вас.
Если хеш-код объекта может изменяться, пока он находится в хеш-таблице, тогда метод Contains перестает работать. Вы помещаете объект в корзину № 5, вы изменяете его, и когда вы спрашиваете набор, содержит ли он мутировавший объект, он смотрит в корзину № 74 и не находит его.
Помните, что объекты могут быть помещены в хеш-таблицы способами, которые вы не ожидали. Многие операторы последовательности LINQ внутренне используют хеш-таблицы. Не используйте опасно мутирующие объекты при перечислении запроса LINQ, который их возвращает!
РЕДАКТИРОВАТЬ: Кстати, ваше сообщение и последующее редактирование - прекрасный пример того, почему вы всегда должны публиковать полный и воспроизводимый рабочий код вашей проблемы с самого начала, а не пытаться отфильтровать то, что, по вашему мнению, является неактуальной информацией. Почти каждый, кто просматривал ваше сообщение час назад, мог бы дать вам правильный ответ за долю секунды, если бы у него была вся необходимая информация для начала.