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, который их возвращает!

РЕДАКТИРОВАТЬ: Кстати, ваше сообщение и последующее редактирование - прекрасный пример того, почему вы всегда должны публиковать полный и воспроизводимый рабочий код вашей проблемы с самого начала, а не пытаться отфильтровать то, что, по вашему мнению, является неактуальной информацией. Почти каждый, кто просматривал ваше сообщение час назад, мог бы дать вам правильный ответ за долю секунды, если бы у него была вся необходимая информация для начала.

Другие вопросы по тегам