使用对象的字段作为通用字典键
Posted
技术标签:
【中文标题】使用对象的字段作为通用字典键【英文标题】:Using the field of an object as a generic Dictionary key 【发布时间】:2010-10-12 16:47:00 【问题描述】:如果我想使用对象作为Dictionary
的键,我需要重写哪些方法以使它们以特定方式进行比较?
假设我有一个具有属性的类:
class Foo
public string Name get; set;
public int FooID get; set;
// elided
我想创建一个:
Dictionary<Foo, List<Stuff>>
我希望将具有相同 FooID
的 Foo
对象视为同一组。我需要在 Foo
类中重写哪些方法?
总结一下:我想将Stuff
对象分类为列表,按Foo
对象分组。 Stuff
对象将有一个 FooID
将它们链接到它们的类别。
【问题讨论】:
【参考方案1】:默认情况下,两个重要的方法是GetHashCode()
和Equals()
。重要的是,如果两个事物相等(Equals()
返回 true),它们具有相同的哈希码。例如,您可能会“返回 FooID;”作为GetHashCode()
,如果您希望它作为匹配项。你也可以实现IEquatable<Foo>
,但这是可选的:
class Foo : IEquatable<Foo>
public string Name get; set;
public int FooID get; set;
public override int GetHashCode()
return FooID;
public override bool Equals(object obj)
return Equals(obj as Foo);
public bool Equals(Foo obj)
return obj != null && obj.FooID == this.FooID;
最后,另一种选择是提供IEqualityComparer<T>
来做同样的事情。
【讨论】:
+1 我并不是要劫持这个线程,但我的印象是 GetHashCode() 应该返回 FooId.GetHashCode()。这不是正确的模式吗? @Ken - 好吧,它只需要返回一个提供必要功能的 int 。哪个 FooID 与 FooID.GetHashCode() 一样好。作为一个实现细节,Int32.GetHashCode() 是“return this;”。对于其他类型(字符串等),是的:.GetHashCode() 将非常有用。 谢谢!我选择了 IEqualityComparer由于您希望 FooID
成为组的标识符,因此您应该将其用作字典中的键,而不是 Foo 对象:
Dictionary<int, List<Stuff>>
如果您使用Foo
对象作为键,您只需实现GetHashCode
和Equals
方法以仅考虑FooID
属性。就Dictionary
而言,Name
属性只是自重,因此您只需使用Foo
作为int
的包装器。
因此最好直接使用FooID
值,然后您不必实现任何东西,因为Dictionary
已经支持使用int
作为键。
编辑:
如果你想使用Foo
类作为key,IEqualityComparer<Foo>
很容易实现:
public class FooEqualityComparer : IEqualityComparer<Foo>
public int GetHashCode(Foo foo) return foo.FooID.GetHashCode();
public bool Equals(Foo foo1, Foo foo2) return foo1.FooID == foo2.FooID;
用法:
Dictionary<Foo, List<Stuff>> dict = new Dictionary<Foo, List<Stuff>>(new FooEqualityComparer());
【讨论】:
更准确地说,int 已经支持将其用作键所需的方法/接口。字典不直接了解 int 或任何其他类型。 我想过这个,但由于各种原因,使用对象作为字典键更清洁、更方便。 好吧,看起来您只是使用对象作为键,因为您实际上只是使用 id 作为键。【参考方案3】:对于 Foo,您需要覆盖 object.GetHashCode() 和 object.Equals()
字典将调用 GetHashCode() 来计算每个值的哈希桶,并调用 Equals 来比较两个 Foo 是否相同。
确保计算好的哈希码(避免许多相等的 Foo 对象具有相同的哈希码),但要确保两个相等的 Foo 具有相同的哈希码。您可能希望从 Equals 方法开始,然后(在 GetHashCode() 中)异或在 Equals 中比较的每个成员的哈希码。
public class Foo
public string A;
public string B;
override bool Equals(object other)
var otherFoo = other as Foo;
if (otherFoo == null)
return false;
return A==otherFoo.A && B ==otherFoo.B;
override int GetHashCode()
return 17 * A.GetHashCode() + B.GetHashCode();
【讨论】:
除此之外 - 但是 xor (^) 对哈希码的组合很差,因为它经常导致很多对角线冲突(即 "foo","bar" vs "bar" ,"foo"。更好的选择是将每个项相乘和相加 - 即 17 * a.GetHashCode() + B.GetHashCode(); 马克,我明白你的意思了。但是你是怎么得到这个神奇的数字 17 的呢?使用素数作为组合哈希的乘数是否有利?如果是,为什么? 我可以建议返回:(A + B).GetHashCode() 而不是:17 * A.GetHashCode() + B.GetHashCode() 这将:1)不太可能发生冲突和 2) 确保没有整数溢出。 (A + B).GetHashCode() 是一个非常糟糕的散列算法,因为如果 (A, B) 的不同集合连接到相同的字符串,它们会产生相同的散列; "hellow" + "ned" 与 "hell" + "owned" 相同,并且会产生相同的哈希值。 @kaesve (A+" "+B).GetHashCode() 怎么样?【参考方案4】:Hashtable
类呢!
Hashtable oMyDic = new Hashtable();
Object oAnyKeyObject = null;
Object oAnyValueObject = null;
oMyDic.Add(oAnyKeyObject, oAnyValueObject);
foreach (DictionaryEntry de in oMyDic)
// Do your job
通过上述方式,您可以使用任何对象(您的类对象)作为通用字典键:)
【讨论】:
【参考方案5】:我遇到了同样的问题。由于覆盖了 Equals 和 GetHashCode,我现在可以使用我尝试过的任何对象作为键。
这是一个类,我使用在 Equals(object obj) 和 GetHashCode() 的覆盖内使用的方法构建。我决定使用应该能够覆盖大多数对象的泛型和散列算法。如果您在此处发现不适用于某些类型的对象并且您有办法改进它,请告诉我。
public class Equality<T>
public int GetHashCode(T classInstance)
List<FieldInfo> fields = GetFields();
unchecked
int hash = 17;
foreach (FieldInfo field in fields)
hash = hash * 397 + field.GetValue(classInstance).GetHashCode();
return hash;
public bool Equals(T classInstance, object obj)
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
if (classInstance.GetType() != obj.GetType())
return false;
return Equals(classInstance, (T)obj);
private bool Equals(T classInstance, T otherInstance)
List<FieldInfo> fields = GetFields();
foreach (var field in fields)
if (!field.GetValue(classInstance).Equals(field.GetValue(otherInstance)))
return false;
return true;
private List<FieldInfo> GetFields()
Type myType = typeof(T);
List<FieldInfo> fields = myType.GetTypeInfo().DeclaredFields.ToList();
return fields;
这是它在类中的使用方式:
public override bool Equals(object obj)
return new Equality<ClassName>().Equals(this, obj);
public override int GetHashCode()
unchecked
return new Equality<ClassName>().GetHashCode(this);
【讨论】:
以上是关于使用对象的字段作为通用字典键的主要内容,如果未能解决你的问题,请参考以下文章
在 C# 中:如何声明一个类型为键的通用字典,该类型的 IEnumerable<> 作为值?