.net - values - dictionary+c#
Usar el campo de un objeto como una clave de diccionario genérica (5)
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<Stuff>>
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.
¿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 :)
Como desea que FooID
sea el identificador del grupo, debe usarlo como clave en el diccionario en lugar del objeto Foo:
Dictionary<int, List<Stuff>>
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<Foo>
es fácil de implementar:
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; }
}
Uso:
Dictionary<Foo, List<Stuff>> dict = new Dictionary<Foo, List<Stuff>>(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();
}
}
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<Foo>
, pero eso es opcional:
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;
}
}
Finalmente, otra alternativa es proporcionar un IEqualityComparer<T>
para hacer lo mismo.
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 genéricos 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<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;
}
}
Así es como se usa en una clase:
public override bool Equals(object obj)
{
return new Equality<ClassName>().Equals(this, obj);
}
public override int GetHashCode()
{
unchecked
{
return new Equality<ClassName>().GetHashCode(this);
}
}