Usar el campo de un objeto como una clave de diccionario genérica

Si quiero usar objetos como las claves de un Dictionary , ¿qué métodos debo anular para hacer que se comparen de una manera específica?

Digamos que tengo una clase que tiene propiedades:

 class Foo { public string Name { get; set; } public int FooID { get; set; } // elided } 

Y quiero crear un:

 Dictionary<Foo, List> 

Quiero que los objetos Foo con el mismo FooID sean considerados el mismo grupo. ¿Qué métodos necesitaré anular en la clase Foo ?

Para resumir: quiero categorizar objetos Stuff en listas agrupadas por objetos Foo . Stuff objetos Stuff tendrán un FooID para vincularlos a su categoría.

Por defecto, los dos métodos importantes son GetHashCode() e Equals() . Es importante que si dos cosas son iguales ( Equals() devuelve verdadero), tengan el mismo código hash. Por ejemplo, puede “devolver FooID”; como GetHashCode() si quieres eso como la coincidencia. También puede implementar IEquatable , pero eso es opcional:

 class Foo : IEquatable { 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; } } 

Finalmente, otra alternativa es proporcionar un IEqualityComparer para hacer lo mismo.

Como desea que FooID sea ​​el identificador del grupo, debe usarlo como clave en el diccionario en lugar del objeto Foo:

 Dictionary> 

Si utilizara el objeto Foo como clave, simplemente implementaría el método GetHashCode y Equals para considerar solo la propiedad FooID . La propiedad Name solo sería un peso muerto en lo que respecta al Dictionary , por lo que solo usaría Foo como un contenedor para un int .

Por lo tanto, es mejor usar el valor de FooID directamente, y luego no tiene que implementar nada ya que el Dictionary ya admite el uso de una clave int .

Editar:
Si desea utilizar la clase Foo como clave de todos modos, IEqualityComparer es fácil de implementar:

 public class FooEqualityComparer : IEqualityComparer { public int GetHashCode(Foo foo) { return foo.FooID.GetHashCode(); } public bool Equals(Foo foo1, Foo foo2) { return foo1.FooID == foo2.FooID; } } 

Uso:

 Dictionary> dict = new Dictionary>(new FooEqualityComparer()); 

Para Foo, tendrá que anular object.GetHashCode () y object.Equals ()

El diccionario llamará a GetHashCode () para calcular un cubo de hash para cada valor e Igual que para comparar si dos Foo son idénticos.

Asegúrese de calcular buenos códigos hash (evite muchos objetos Foo iguales que tengan el mismo código hash), pero asegúrese de que dos Foos iguales tengan el mismo código hash. Es posible que desee comenzar con Equals-Method y luego (en GetHashCode ()) xor el código hash de cada miembro que compare en 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(); } } 

¿Qué hay de la clase Hashtable ?

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

De manera anterior, puede usar cualquier objeto (su objeto de clase) como una clave de diccionario genérica 🙂

Yo tuve el mismo problema. Ahora puedo usar cualquier objeto que haya intentado como clave debido a la anulación de Iguales y GetHashCode.

Aquí hay una clase que construí con métodos para usar dentro de las anulaciones de Equals (object obj) y GetHashCode (). Decidí usar generics y un algoritmo hash que debería poder cubrir la mayoría de los objetos. Por favor, avíseme si ve algo aquí que no funciona para algunos tipos de objetos y tiene una forma de mejorarlo.

 public class Equality { public int GetHashCode(T classInstance) { List 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 fields = GetFields(); foreach (var field in fields) { if (!field.GetValue(classInstance).Equals(field.GetValue(otherInstance))) { return false; } } return true; } private List GetFields() { Type myType = typeof(T); List fields = myType.GetTypeInfo().DeclaredFields.ToList(); return fields; } } 

Así es como se usa en una clase:

 public override bool Equals(object obj) { return new Equality().Equals(this, obj); } public override int GetHashCode() { unchecked { return new Equality().GetHashCode(this); } }