可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
If I want to use objects as the keys for a Dictionary
, what methods will I need to override to make them compare in a specific way?
Say I have a a class which has properties:
class Foo {
public string Name { get; set; }
public int FooID { get; set; }
// elided
}
And I want to create a:
Dictionary<Foo, List<Stuff>>
I want Foo
objects with the same FooID
to be considered the same group. Which methods will I need to override in the Foo
class?
To summarize: I want to categorize Stuff
objects into lists, grouped by Foo
objects. Stuff
objects will have a FooID
to link them to their category.
回答1:
By default, the two important methods are GetHashCode()
and Equals()
. It is important that if two things are equal (Equals()
returns true), that they have the same hash-code. For example, you might \"return FooID;\" as the GetHashCode()
if you want that as the match. You can also implement IEquatable<Foo>
, but that is optional:
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;
}
}
Finally, another alternative is to provide an IEqualityComparer<T>
to do the same.
回答2:
As you want the FooID
to be the identifier for the group, you should use that as key in the dictionary instead of the Foo object:
Dictionary<int, List<Stuff>>
If you would use the Foo
object as key, you would just implement the GetHashCode
and Equals
method to only consider the FooID
property. The Name
property would just be dead weight as far as the Dictionary
was concerned, so you would just use Foo
as a wrapper for an int
.
Therefore it\'s better to use the FooID
value directly, and then you don\'t have to implement anything as the Dictionary
already supports using an int
as a key.
Edit:
If you want to use the Foo
class as key anyway, the IEqualityComparer<Foo>
is easy to implement:
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; }
}
Usage:
Dictionary<Foo, List<Stuff>> dict = new Dictionary<Foo, List<Stuff>>(new FooEqualityComparer());
回答3:
For Foo you will need to override object.GetHashCode() and object.Equals()
The dictionary will call GetHashCode() to calculate a hash bucket for each value and Equals to compare whether two Foo\'s are identical.
Make sure to calculate good hash codes (avoid many equal Foo objects having the same hashcode), but make sure two equals Foos have the same hash code. You might want to start with the Equals-Method and then (in GetHashCode()) xor the hash code of every member you compare in 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();
}
}
回答4:
What about Hashtable
class!
Hashtable oMyDic = new Hashtable();
Object oAnyKeyObject = null;
Object oAnyValueObject = null;
oMyDic.Add(oAnyKeyObject, oAnyValueObject);
foreach (DictionaryEntry de in oMyDic)
{
// Do your job
}
In above way, you can use any object (your class object) as a generic Dictionary key :)
回答5:
I had the same problem. I can now use any object I\'ve tried as a key due to overriding Equals and GetHashCode.
Here is a class that I built with methods to use inside of the overrides of Equals(object obj) and GetHashCode(). I decided to use generics and a hashing algorithm that should be able to cover most objects. Please let me know if you see anything here that doesn\'t work for some types of object and you have a way to improve it.
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;
}
}
Here is how it\'s used in a class:
public override bool Equals(object obj)
{
return new Equality<ClassName>().Equals(this, obj);
}
public override int GetHashCode()
{
unchecked
{
return new Equality<ClassName>().GetHashCode(this);
}
}