Varias funciones Linq.Enumerable toman un IEqualityComparer<T>
. ¿Hay una clase de contenedor conveniente que se adapte delegate(T,T)=>bool
a implementar IEqualityComparer<T>
? Es bastante fácil escribir uno (si ignora los problemas al definir un código hash correcto), pero me gustaría saber si hay una solución lista para usar.
Específicamente, quiero hacer operaciones de configuración en Dictionary
s, usando solo las Claves para definir la membresía (al tiempo que conserva los valores de acuerdo con diferentes reglas).
IEqualityComparer<T>
que quedaGetHashCode
fuera está simplemente roto.Sobre la importancia de
GetHashCode
Otros ya han comentado el hecho de que cualquier
IEqualityComparer<T>
implementación personalizada realmente debería incluir unGetHashCode
método ; pero nadie se molestó en explicar por qué con ningún detalle.Este es el por qué. Su pregunta menciona específicamente los métodos de extensión LINQ; Casi todos estos dependen de códigos hash para funcionar correctamente, ya que utilizan tablas hash internamente para mayor eficiencia.
Toma
Distinct
, por ejemplo. Considere las implicaciones de este método de extensión si todo lo que utilizara fuera unEquals
método. ¿Cómo determina si un elemento ya ha sido escaneado en una secuencia si solo lo ha hechoEquals
? Enumera toda la colección de valores que ya ha visto y busca una coincidencia. ¡Esto daría como resultado elDistinct
uso de un algoritmo O (N 2 ) en el peor de los casos en lugar de uno O (N)!Afortunadamente, este no es el caso.
Distinct
no solo usaEquals
; Se usaGetHashCode
también. De hecho, absolutamente no funciona correctamente sin unIEqualityComparer<T>
que proporcione un adecuadoGetHashCode
. A continuación se muestra un ejemplo artificial que ilustra esto.Digamos que tengo el siguiente tipo:
Ahora di que tengo un
List<Value>
y quiero encontrar todos los elementos con un nombre distinto. Este es un caso de uso perfecto paraDistinct
usar un comparador de igualdad personalizado. Entonces usemos laComparer<T>
clase de la respuesta de Aku :Ahora, si tenemos un montón de
Value
elementos con la mismaName
propiedad, todos deberían colapsar en un valor devuelto porDistinct
, ¿verdad? Veamos...Salida:
Hmm, eso no funcionó, ¿verdad?
¿Qué hay de
GroupBy
? Probemos eso:Salida:
De nuevo: no funcionó.
Si lo piensa, tendría sentido
Distinct
usar unHashSet<T>
(o equivalente) internamente, yGroupBy
usar algo como unDictionary<TKey, List<T>>
interno. ¿Podría esto explicar por qué estos métodos no funcionan? Intentemos esto:Salida:
Sí ... ¿empieza a tener sentido?
Esperemos que de estos ejemplos quede claro por qué es tan importante incluir un apropiado
GetHashCode
en cualquierIEqualityComparer<T>
implementación.Respuesta original
Ampliando la respuesta de orip :
Hay un par de mejoras que se pueden hacer aquí.
Func<T, TKey>
lugar deFunc<T, object>
; Esto evitará el encajonamiento de las claves de tipo de valor en elkeyExtractor
propio.where TKey : IEquatable<TKey>
restricción; esto evitará el encajonamiento en laEquals
llamada (object.Equals
toma unobject
parámetro; necesita unaIEquatable<TKey>
implementación para tomar unTKey
parámetro sin encajonarlo). Claramente, esto puede suponer una restricción demasiado severa, por lo que podría hacer una clase base sin la restricción y una clase derivada con ella.Así es como se vería el código resultante:
fuente
StrictKeyEqualityComparer.Equals
método parece ser el mismo queKeyEqualityComparer.Equals
. ¿LaTKey : IEquatable<TKey>
restricción hace que elTKey.Equals
trabajo sea diferente?TKey
puede ser cualquier tipo arbitrario, el compilador utilizar el método virtualObject.Equals
que requerirá boxeo de parámetros de tipo de valor, por ejemplo,int
. En el último caso, sin embargo, dado queTKey
está obligado a implementarIEquatable<TKey>
,TKey.Equals
se utilizará el método que no requerirá ningún boxeo.StringKeyEqualityComparer<T, TKey>
también.Cuando desee personalizar la comprobación de igualdad, el 99% de las veces le interesa definir las claves para comparar, no la comparación en sí.
Esta podría ser una solución elegante (concepto del método de clasificación de listas de Python ).
Uso:
La
KeyEqualityComparer
clase:fuente
Func<T, int>
para proporcionar el código hash para unT
valor (como se ha sugerido, por ejemplo, en la respuesta de Ruben ). De lo contrario, laIEqualityComparer<T>
implementación que le queda está bastante rota, especialmente con respecto a su utilidad en los métodos de extensión LINQ. Vea mi respuesta para una discusión sobre por qué es esto.Me temo que no hay tal envoltorio fuera de la caja. Sin embargo, no es difícil crear uno:
fuente
private readonly Func<T, int> _hashCodeResolver
que también debe pasarse en el constructor y usarse en elGetHashCode(...)
método.obj.ToString().ToLower().GetHashCode()
lugar deobj.GetHashCode()
?IEqualityComparer<T>
hash invariablemente usan detrás de escena (por ejemplo, GroupBy, Distinct, Except, Join, etc. de LINQ) y el contrato de MS con respecto al hashing se rompe en esta implementación. Aquí está el extracto de la documentación de MS: "Se requieren implementaciones para garantizar que si el método Equals devuelve verdadero para dos objetos x e y, entonces el valor devuelto por el método GetHashCode para x debe ser igual al valor devuelto para y". Ver: msdn.microsoft.com/en-us/library/ms132155Igual que la respuesta de Dan Tao, pero con algunas mejoras:
Se basa en
EqualityComparer<>.Default
la comparación real para evitar el boxeo de los tipos de valorstruct
que se han implementadoIEquatable<>
.Desde que se
EqualityComparer<>.Default
usa no explotanull.Equals(something)
.Proporcionó un contenedor estático alrededor del
IEqualityComparer<>
cual tendrá un método estático para crear la instancia de comparador - facilita la llamada. Compararcon
Se agregó una sobrecarga para especificar
IEqualityComparer<>
la clave.La clase:
puedes usarlo así:
La persona es una clase simple:
fuente
Con extensiones: -
fuente
La respuesta de orip es genial.
Aquí un pequeño método de extensión para hacerlo aún más fácil:
fuente
Voy a responder mi propia pregunta. Para tratar los diccionarios como conjuntos, el método más simple parece ser aplicar operaciones de conjunto a dict.Keys, luego convertir de nuevo a Diccionarios con Enumerable.ToDictionary (...).
fuente
La implementación en (texto en alemán) Implementando IEqualityCompare con la expresión lambda se preocupa por los valores nulos y utiliza métodos de extensión para generar IEqualityComparer.
Para crear un IEqualityComparer en una unión Linq solo tienes que escribir
El comparador:
También debe agregar un método de extensión para admitir la inferencia de tipos
fuente
Solo una optimización: podemos usar el EqualityComparer listo para usar para realizar comparaciones de valores, en lugar de delegarlo.
Esto también haría que la implementación sea más limpia, ya que la lógica de comparación real ahora permanece en GetHashCode () y Equals (), que ya puede haber sobrecargado.
Aquí está el código:
No olvides sobrecargar los métodos GetHashCode () y Equals () en tu objeto.
Esta publicación me ayudó: c # compara dos valores genéricos
Sushil
fuente
La respuesta de Orip es genial. Ampliando la respuesta de orip:
Creo que la clave de la solución es utilizar el "Método de extensión" para transferir el "tipo anónimo".
Uso:
fuente
Esto permite seleccionar una propiedad con lambda como esta:
.Select(y => y.Article).Distinct(x => x.ArticleID);
fuente
No sé de una clase existente pero algo como:
Nota: todavía no he compilado y ejecutado esto, por lo que puede haber un error tipográfico u otro error.
fuente