我有一个基本项的列表(准确地来自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<>
,则代码可以正常运行,所以如果你们没有想出任何好主意,我会切换到列表。
答案 0 :(得分: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()
调用的RemoveItem()
中的 DoStuff()
将为每个项目返回false,因为您更改了存储项目的标识 - 其BaseString
属性
答案 1 :(得分:3)
我认为您在编辑中回答了自己的问题。将项目添加到HashSet后,您无法更改hashCode。这打破了HashSet如何工作的契约。
有关该主题的更多信息,请参阅this excellent article by Eric Lippert。
特别是,它说:
指南:GetHashCode返回的整数永远不会改变
理想情况下,可变对象的哈希码应仅从不能变异的字段计算,因此对象的哈希值在其整个生命周期内都是相同的。
但是,这只是一个理想的情况指南;实际的规则是:
规则:当对象包含在依赖于哈希码保持稳定的数据结构中时,GetHashCode返回的整数必须永远不会改变
允许(尽管很危险)使一个对象的哈希码值可以随着对象的字段变异而变异。如果你有这样一个对象,并把它放在一个哈希表中,那么改变对象的代码和维护哈希表的代码需要有一些商定的协议,以确保对象在进入时不会发生变异。哈希表。该协议的外观取决于您。
如果对象的哈希码在哈希表中变异,那么显然包含方法将停止工作 。你将对象放在#5桶中,你将它变异,当你询问它是否包含变异对象时,它会在#74桶中查找并且找不到它。
请记住,对象可以以您不期望的方式放入哈希表中。许多LINQ序列运算符在内部使用哈希表。在枚举返回它们的LINQ查询时,不要危险地改变对象!
编辑:顺便说一句,您的帖子以及随后的编辑都是一个很好的例子,说明为什么您应该始终从一开始就发布问题的完整且可重现的工作代码,而不是尝试过滤掉什么你觉得这是无关紧要的信息。几乎所有在一小时前查看你的帖子的人都可以在一瞬间给你正确答案,如果他们有所有相关信息的话。