簡體   English   中英

ImmutableHashSet .Contains返回false

[英]ImmutableHashSet .Contains returns false

我有一個基本項的列表(確切地說是來自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
}

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<> ,代碼工作正常,所以如果你們沒有提出任何好主意,我將切換到列表。

在字典和其他與散列相關的數據結構中使用的對象應該具有不可變的標識 - 所有與散列相關的數據結構都假設一旦將對象添加到字典中,其哈希碼就不會改變。

這段代碼不起作用:

    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);
        }
    }

DoStuff()調用的RemoveItem() _baseList.Contains()將為每個項返回false,因為您更改了存儲項的標識 - 其BaseString屬性。

我想你在編輯中回答了你自己的問題。 將項目添加到HashSet后,您無法更改hashCode。 這打破了HashSet如何工作的契約。

有關主題的更多信息,請參閱Eric Lippert撰寫的這篇優秀文章

特別是,它說如下:

准則:GetHashCode返回的整數永遠不會改變

理想情況下,可變對象的哈希碼應僅從不能變異的字段計算,因此對象的哈希值在其整個生命周期內都是相同的。

但是,這只是一個理想的情況指南; 實際的規則是:

規則:當對象包含在依賴於哈希代碼保持穩定的數據結構中時,GetHashCode返回的整數必須永遠不會更改

雖然很危險,但允許一個對象的哈希碼值可以隨着對象的字段變異而變異。 如果你有這樣一個對象,並把它放在一個哈希表中,那么改變對象的代碼和維護哈希表的代碼需要有一些商定的協議,以確保對象在進入時不會發生變異。哈希表。 該協議的外觀取決於您。

如果對象的哈希代碼在哈希表中變異,那么顯然Contains方法就會停止工作 你把對象放在#5桶中,你改變它,當你問集合是否包含變異對象時,它會在#74桶中查找並找不到它。

請記住,對象可以以您不期望的方式放入哈希表中。 許多LINQ序列運算符在內部使用哈希表。 在枚舉返回它們的LINQ查詢時,不要危險地改變對象!

編輯 :BTW,您的帖子和隨后的編輯是一個完美的例子,說明為什么您應該始終從頭開始發布完整且可重現的問題的工作代碼,而不是試圖過濾掉您認為不相關的信息。 幾個小時前看你的帖子的人幾乎可以在一瞬間給你正確的答案,如果他們有所有相關的信息開始。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM