ImmutableHashSet .Contains returns false

ImmutableHashSet .Contains returns false

我有一个基本项目列表(准确地说 ImmutableHashSet<ListItem> 来自 System.Collections.Immutable)并尝试调用以下代码

_baseList.Contains(derivedItem)

但是这个 returns false.

即使下面的代码行都是 return true

object.ReferenceEquals(_baseList.First(), derivedItem)
object.Equals(_baseList.First(), derivedItem)
_baseList.First().GetHashCode() == derivedItem.GetHashCode()

我什至可以写出以下内容并且它return是正确的:

_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
}

编辑2:

这是我的问题的可重现代码,似乎 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<> 代码工作正常,所以如果你们没有想出任何好主意,我会切换到列表。

在字典和其他与散列相关的数据结构中使用的对象应该具有不可变的身份——所有与散列相关的数据结构都假定一旦您将对象添加到字典中,其散列码就不会改变。

此代码无效:

    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);
        }
    }
RemoveItem() 中的

_baseList.Contains(),正如 DoStuff() 所调用的那样,每个项目都会 return false,因为您更改了存储项目的身份 - 它BaseString 属性.

我认为您在编辑中回答了自己的问题。一旦将项目添加到 HashSet 中,就不能更改 hashCode。这打破了 HashSet 工作方式的约定。

有关该主题的更多信息,请参阅 this excellent article by Eric Lippert

特别是,它说了以下内容:

Guideline: the integer returned by GetHashCode should never change

Ideally, the hash code of a mutable object should be computed from only fields which cannot mutate, and therefore the hash value of an object is the same for its entire lifetime.

However, this is only an ideal-situation guideline; the actual rule is:

Rule: the integer returned by GetHashCode must never change while the object is contained in a data structure that depends on the hash code remaining stable

It is permissible, though dangerous, to make an object whose hash code value can mutate as the fields of the object mutate. If you have such an object and you put it in a hash table then the code which mutates the object and the code which maintains the hash table are required to have some agreed-upon protocol that ensures that the object is not mutated while it is in the hash table. What that protocol looks like is up to you.

If an object's hash code can mutate while it is in the hash table then clearly the Contains method stops working. You put the object in bucket #5, you mutate it, and when you ask the set whether it contains the mutated object, it looks in bucket #74 and doesn't find it.

Remember, objects can be put into hash tables in ways that you didn't expect. A lot of the LINQ sequence operators use hash tables internally. Don't go dangerously mutating objects while enumerating a LINQ query that returns them!

编辑:顺便说一句,您的 post 和您随后的编辑是一个完美的例子,说明了为什么您应该始终 post 完整且可重现的工作代码你的问题从头开始,而不是试图过滤掉你觉得不相关的信息。如果一小时前看过你的 post 的人几乎都能在一瞬间给你正确答案,但他们有所有相关信息。