2009-03-11 3 views
101

Если я хочу использовать объекты в качестве ключей для 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, чтобы скрыть их к категории.

ответ

128

По умолчанию два важных метода являются 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> сделать то же самое.

+4

+1 и я не хочу увлекать эту тему, но у меня создалось впечатление, что GetHashCode() должен возвращать FooId.GetHashCode(). Это не правильный шаблон? –

+7

@ Ken - ну, ему просто нужно вернуть int, который предоставляет необходимые функции. Какой FooID будет делать так же, как FooID.GetHashCode(). В качестве детали реализации Int32.GetHashCode() является «return this;». Для других типов (строка и т. Д.), Тогда да: .GetHashCode() будет очень полезен. –

+2

Спасибо! Я пошел с IEqualityComparer , поскольку только для Дикарионари мне нужны были переопределенные методы. – Dana

8

Для 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(); 
    } 
} 
+2

Помимо - но исключающее (^) делает бедный комбинатор для хэш-кодов, как это часто приводит к большому количеству диагональных столкновений (т.е. {"foo", "bar"} vs {"bar", "foo"}. Лучшим выбором является умножение и добавление каждого термина - например, 17 * a.GetHashCode() + B.GetHashCode(); –

+2

Marc, I посмотрите, что вы имеете в виду. Но как вы попадаете на магическое число 17? Полезно ли использовать простое число как мультипликатор для объединения хэшей? Если да, то почему? – froh42

+0

Могу ли я предложить вернуть: (A + B) .GetHashCode() , а не: 17 * A.GetHashCode() + B.GetHashCode() Это будет: 1) иметь меньше шансов на столкновение и 2) гарантировать, что нет целочисленного переполнения. –

29

Как вы хотите 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()); 
+1

Вернее, int уже поддерживает методы/интерфейсы, необходимые для его использования в качестве ключа. Словарь не имеет прямого знания о int или любом другом типе. –

+0

Я подумал об этом, но по целому ряду причин было проще и удобнее использовать объекты в качестве словарных клавиш. – Dana

+1

Ну, похоже, что вы используете объект как ключ, так как на самом деле вы используете только ключ id. – Guffa

1

насчет Hashtable класс!

Hashtable oMyDic = new Hashtable(); 
Object oAnyKeyObject = null; 
Object oAnyValueObject = null; 
oMyDic.Add(oAnyKeyObject, oAnyValueObject); 
foreach (DictionaryEntry de in oMyDic) 
{ 
    // Do your job 
} 

В вышеописанным способом, вы можете использовать любой объект (ваш класс объекта) как общий ключ словаря :)

0

У меня была такая же проблема. Теперь я могу использовать любой объект, который я пытался использовать в качестве ключа из-за переопределения 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); 
     } 
    } 
Смежные вопросы