2009-01-11 18 views
51

Esto es lo que estoy tratando de hacer. Estoy consultando un archivo XML usando LINQ to XML, que me da un objeto IEnumerable <T>, donde T es mi clase "Village", llena con los resultados de esta consulta. Algunos resultados se duplican, por lo que me gustaría realizar una Distinto() en el objeto IEnumerable, así:C# Distinto en IEnumerable <T> con IEqualityComparer personalizado

public IEnumerable<Village> GetAllAlliances() 
{ 
    try 
    { 
     IEnumerable<Village> alliances = 
      from alliance in xmlDoc.Elements("Village") 
      where alliance.Element("AllianceName").Value != String.Empty 
      orderby alliance.Element("AllianceName").Value 
      select new Village 
      { 
       AllianceName = alliance.Element("AllianceName").Value 
      }; 

     // TODO: make it work... 
     return alliances.Distinct(new AllianceComparer()); 
    } 
    catch (Exception ex) 
    { 
     throw new Exception("GetAllAlliances", ex); 
    } 
} 

Como el comparador predeterminado no funcionaría para el objeto Village, he implementado una personalizada, como se ha visto aquí en la clase AllianceComparer:

public class AllianceComparer : IEqualityComparer<Village> 
{ 
    #region IEqualityComparer<Village> Members 
    bool IEqualityComparer<Village>.Equals(Village x, Village y) 
    { 
     // Check whether the compared objects reference the same data. 
     if (Object.ReferenceEquals(x, y)) 
      return true; 

     // Check whether any of the compared objects is null. 
     if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) 
      return false; 

     return x.AllianceName == y.AllianceName; 
    } 

    int IEqualityComparer<Village>.GetHashCode(Village obj) 
    { 
     return obj.GetHashCode(); 
    } 
    #endregion 
} 

el método distinct() no funciona, ya que tengo exactamente el mismo número de resultados con o sin él. Otra cosa, y no sé si generalmente es posible, pero no puedo entrar en AllianceComparer.Equals() para ver cuál podría ser el problema.
He encontrado ejemplos de esto en Internet, pero parece que no puedo hacer que mi implementación funcione.

¡Con suerte, alguien aquí podría ver lo que podría estar mal aquí! ¡Gracias de antemano!

+0

Tu construcción catch/throw hace que la función de llamada ya no pueda elegir capturar (ArgumentException) o catch (IOException) (ejemplos). En este caso, probablemente sea mejor que elimines el try/catch todos juntos; además, el nombre del método será parte de la propiedad de StackTrace de excepción. –

Respuesta

72

El problema está en su GetHashCode. Debería modificarlo para devolver el código hash de AllianceName.

int IEqualityComparer<Village>.GetHashCode(Village obj) 
{ 
    return obj.AllianceName.GetHashCode(); 
} 

La cosa es, si Equals vuelve true, los objetos deben tener el mismo código hash que no es el caso para los diferentes objetos con Village misma AllianceName. Dado que Distinct funciona construyendo una tabla hash internamente, terminará con objetos iguales que no se emparejarán en absoluto debido a diferentes códigos hash.

De forma similar, para comparar dos archivos, si el hash de dos archivos no es el mismo, no es necesario que compruebe los archivos en sí mismos. Ellos serán ser diferentes. De lo contrario, continuará verificando si realmente son iguales o no. Eso es exactamente lo que se comporta la tabla hash que usa Distinct.

+0

¿Por qué no podemos anular los métodos de igualdad típicos para Distinct? – Boog

+0

@Boog Por supuesto, puede hacer esto en el objeto mismo o en una clase de comparación de igualdad separada, como lo quiere el OP. El caso de uso para un comparador de igualdad personalizado es cuando tiene diferentes formas de considerar objetos iguales o no (por ejemplo, comparación de mayúsculas y minúsculas insensible a mayúsculas y minúsculas) o cuando no puede cambiar la clase en sí por cualquier motivo. En cualquier caso, debe anular tanto 'Equals' como' GetHashCode' y escribir un 'GetHashCode' apropiado con respecto al método' Equals'. –

6

O cambiar la línea

return alliances.Distinct(new AllianceComparer()); 

a

return alliances.Select(v => v.AllianceName).Distinct(); 
+0

Este es un enfoque alternativo interesante que haría que el que hace la pregunta repensara el código que está utilizando el IEqualityComparer. Esto resuelve un problema para mí, y es por eso que me gusta. Sin embargo, no responde la pregunta. En mi caso, quería extraer claves únicas de una colección de datos clave-valor, y almacenar las claves únicas por separado en una lista. Como se mencionó en @superrcat, el tipo genérico del IEnumerable devuelto se modifica al hacer esto. – Greg

10

return alliances.Select(v => v.AllianceName).Distinct();

que devolvería un IEnumerable<string> en lugar de IEnumerable<Village>.

Cuestiones relacionadas