5

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

_baseList.Contains(derivedItem)

但这返回false

即使以下代码行都返回true

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

我什至可以编写以下内容并返回 true:

_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<>正常工作,那么如果你们没有提出任何好主意,我将切换到列表。

4

2 回答 2

4

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

此代码不起作用:

    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()in RemoveItem(), as called byDoStuff()将为每个项目返回 false ,因为您更改了存储项目的标识 - 它的BaseString属性。

于 2015-06-04T14:37:12.477 回答
4

我认为您在编辑中回答了自己的问题。将项目添加到 HashSet 后,您将无法更改 hashCode。这违反了 HashSet 如何工作的约定。

有关该主题的更多信息,请参阅Eric Lippert 的这篇优秀文章。

特别是,它说以下内容:

准则:GetHashCode 返回的整数永远不应该改变

理想情况下,可变对象的哈希码应该只从不能改变的字段中计算出来,因此对象的哈希值在其整个生命周期内都是相同的。

然而,这只是一个理想情况的指导方针;实际规则是:

规则:当对象包含在依赖于哈希码保持稳定的数据结构中时,GetHashCode 返回的整数永远不能改变

虽然很危险,但允许对象的哈希码值随着对象的字段发生变异而发生变异是允许的。如果你有这样一个对象并且你把它放在一个哈希表中,那么改变对象的代码和维护哈希表的代码需要有一些商定的协议,以确保对象在它存在时不会发生变异哈希表。该协议的外观取决于您。

如果对象的哈希码在哈希表中时可以发生变异,那么很明显 Contains 方法将停止工作。您将对象放入存储桶 #5,对其进行变异,当您询问集合是否包含变异对象时,它会在存储桶 #74 中查找,但没有找到。

请记住,对象可以以您意想不到的方式放入哈希表中。许多 LINQ 序列运算符在内部使用哈希表。不要在枚举返回对象的 LINQ 查询时危险地改变对象!

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

于 2015-06-04T14:41:16.523 回答