Использование поля объекта в качестве общего ключа словаря
Если я хочу использовать объекты в качестве ключей для Dictionary
, какие методы мне нужно переопределить, чтобы сравнить их определенным образом?
Скажем, у меня есть класс, который имеет свойства:
class Foo {
public string Name { get; set; }
public int FooID { get; set; }
// elided
}
И я хочу создать:
Dictionary<Foo, List<Stuff>>
Я хочу, чтобы объекты Foo
с тем же FooID
считались одной и той же группой. Какие методы мне нужно переопределить в классе 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>
сделать то же самое.
Ответ 2
Как вы хотите, чтобы FooID
был идентификатором для группы, вы должны использовать это как ключ в словаре вместо объекта Foo:
Dictionary<int, List<Stuff>>
Если вы будете использовать объект Foo
как ключ, вы просто реализуете метод GetHashCode
и Equals
, чтобы рассматривать только свойство FooID
. Свойство Name
было бы просто мертвым весом до Dictionary
, поэтому вы просто использовали бы Foo
в качестве обертки для int
.
Поэтому лучше использовать значение FooID
напрямую, а затем вам не нужно ничего реализовывать, поскольку Dictionary
уже поддерживает использование int
в качестве ключа.
Edit:
Если вы все равно хотите использовать класс Foo
как ключ, 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());
Ответ 3
Для Foo вам необходимо переопределить object.GetHashCode() и object.Equals()
Словарь вызовет GetHashCode() для вычисления хэш-ведра для каждого значения и Equals для сравнения того, являются ли два Foo одинаковыми.
Не забудьте рассчитать хорошие хэш-коды (избегайте многих одинаковых объектов Foo с одинаковым хэш-кодом), но убедитесь, что два равных Foos имеют одинаковый хеш-код. Вы можете начать с Equals-Method, а затем (в GetHashCode()) xor хэш-код каждого члена, который вы сравниваете в 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
Что насчет класса 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 (объект obj) и GetHashCode(). Я решил использовать generics и алгоритм хэширования, который должен быть способен охватить большинство объектов. Пожалуйста, дайте мне знать, если вы видите что-то здесь, что не работает для некоторых типов объектов, и у вас есть способ его улучшить.
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);
}
}