【问题标题】:ImmutableHashSet .Contains returns falseImmutableHashSet .Contains 返回 false
【发布时间】:2015-06-04 13:17:41
【问题描述】:

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

_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&lt;&gt; 缓存 GetHashCode 并且不会将当前的 GetHashCode 与列表中的条目进行比较,有没有办法告诉 ImmutableHashSet&lt;&gt; @987654330 @ of the items 可能会有所不同,至少对于我当前正在检查的项目,因为嘿,它是相同的参考...

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&lt;&gt; 更改为 ImmutableList&lt;&gt; 代码可以正常工作,所以如果你们没有任何好主意,我将切换到列表。

【问题讨论】:

  • 但它不只是失败,因为 baseList 充满了ListItem,而您要搜索的对象是DerivedClass
  • 你是否重写了任何相等方法?
  • BaseClassDerivedClass定义了哪些相等方法? HashSet中使用的KeyComparer是什么?
  • @usr 是的,我确实覆盖了相等方法,但由于所有返回 true,应该没有问题,因为我使用的是 ImmutableHASHSET,它只检查相同的 GetHashCode 还是?
  • 如果要改变内容,不可变集合的意义何在?这没有任何意义,没有什么能阻止我更改集合中每个项目的值!

标签: c# hashset immutable-collections


【解决方案1】:

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

此代码不起作用:

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

【讨论】:

  • 在我看到发生了什么之后,到目前为止我都明白了,所以基本上我现在的问题除了使用 List 之外还有其他方法吗?您的回答暗示了这一点,但也许您可以提前给出更直接的是或否的感谢。 :)
  • @RandRandom 你为什么首先改变集合中的项目(以改变其身份的方式)?
  • @RandRandom - 如果要修改散列对象,则必须将其从散列数据结构中删除、修改并重新添加。然而,这样做真的很糟糕。如果您有一个大型程序并且这些对象到处传递,并且您允许修改对象的身份,那么您将自己设置为失败,因为一些不相关的代码可能会修改散列对象而不通知散列数据结构的所有者。
【解决方案2】:

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

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

特别是,它说:

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

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

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-19
    • 2021-01-28
    • 1970-01-01
    • 2020-07-18
    • 2020-09-15
    • 2011-12-29
    相关资源
    最近更新 更多