我认为这是我们经常遇到的问题。
class Person
{
public string place;
public string name;
public Person(string place, string name)
{
this.place = place;
this.name = name;
}
public bool Equals(Person other)
{
if (ReferenceEquals(null, other))
return false;
return name == other.name;
}
public override bool Equals(object obj)
{
return Equals(obj as Person);
}
public override int GetHashCode()
{
return name.GetHashCode();
}
public override string ToString()
{
return place + " - " + name;
}
}
假设我有这个类。我可以这样实现一个KeyedCollection
:
class Collection : KeyedCollection<string, Person>
{
protected override string GetKeyForItem(Person item)
{
return item.place;
}
}
这里的情况是,默认的Equals
是基于Person
的name
,但在我的情况下,我正在创建一个自定义的collection
,每个place
只有一个Person
。换句话说,place
在collection
中是唯一的。
Person p1 = new Person("Paris", "Paul");
Person p2 = new Person("Dubai", "Ali");
var collection = new Collection { p1, p2 };
var p3 = new Person("Paris", "Jean");
if (!collection.Contains(p3))
collection.Add(p3); // explosion
我明白这个问题。Contains(Person)
的重载是Collection<T>.Contains(T)
的重载,它进行基于值的线性搜索,而Add(Person)
将值添加到内部字典中,这可能导致重复键异常。在这里,如果相等是基于place
,这个问题就不会存在了。
我可以有一个变通办法:
class Collection : KeyedCollection<string, Person>
{
protected override string GetKeyForItem(Person item)
{
return item.place;
}
new public bool Contains(Person item)
{
return this.Contains(GetKeyForItem(item));
}
}
但是这意味着如果我做一个一般的
var p3 = new Person("Paris", "Jean");
bool b = collection.Contains(p3); //true
返回true
,但实际上Jean
还不存在于collection
中。所以我的问题是,只有当Equals
仅仅基于T
的K
部分时,KeyedCollection<K, T>
才有意义吗?我的问题与语义无关。我不是在要求解决方案,但只是知道什么时候KeyedCollection
有意义吗?我在文档中找不到任何与此主题相关的内容。
更新:
我已经找到了这里提到的确切问题http://bytes.com/topic/net/answers/633980-framework-bug-keyedcollection-t
提问者向ms提交了一个bug报告。引用他的话(日期为07年4月18日):
我把这个作为bug提交给了微软,他们已经验证过了接受它。Issue ID 271542,可以在这里追踪:
"我们在WinXP pro SP2和VSTS2005 SP1上复制了这个错误,并且我们正在发送这个错误到适当的组内的视觉Studio Product Team for分流和解决方案。"
虽然我不认为这是一个bug,但这确实是一个烦恼。但是我想知道微软是如何接受这个bug的(预期,这个页面现在找不到)。在我看来,这只是考虑得很差的继承模型。
似乎有两件事是有意义的:
-
是否包含居住在巴黎的名为Jean的人和
-
是否包含一个居住在巴黎的人。
The KeyedCollection<TKey,>类提供了恰好两个方法来询问这些问题:
-
Contains(item)方法和
-
Contains(TKey) Method
住在巴黎的人Jean和住在巴黎的人Paul不是同一个人 (person . equals)。但是如果有一个人住在巴黎,那么根据你的规则就不可能有另一个人住在巴黎。
所以基本上你必须在添加新成员之前问集合正确的问题:
if (!collection.Contains(p3.place))
collection.Add(p3);
为方便起见,可以在类中添加TryAdd方法,如果人被成功添加,或者集合中已经有人居住在同一位置,则返回:
public bool TryAdd(Person person)
{
if (Contains(GetKeyForItem(person)))
return false;
Add(person);
return true;
}