¿Puedo especificar mi comparador de tipo explícito en línea?

Así que .NET 3.0 / 3.5 nos proporciona muchas formas nuevas de consultar, ordenar y manipular datos, gracias a todas las funciones ordenadas proporcionadas con LINQ. A veces, necesito comparar los tipos definidos por el usuario que no tienen un operador de comparación incorporado. En muchos casos, la comparación es realmente simple, algo así como foo1.key? = Foo2.key. En lugar de crear un nuevo IEqualityComparer para el tipo, ¿puedo simplemente especificar la comparación en línea utilizando delegaciones anónimas / funciones lambda? Algo como:

var f1 = ..., f2 = ...; var f3 = f1.Except( f2, new IEqualityComparer( (Foo a, Foo b) => a.key.CompareTo(b.key) ) ); 

Estoy bastante seguro de que lo anterior en realidad no funciona. Simplemente no quiero tener que hacer algo tan “pesado” como una clase entera solo para decirle al progtwig cómo comparar manzanas con manzanas.

Mi biblioteca MiscUtil contiene un ProjectionComparer para construir un IComparer de un delegado de proyección. Sería el trabajo de 10 minutos hacer un ProjectionEqualityComparer para hacer lo mismo.

EDITAR: Aquí está el código para ProjectionEqualityComparer:

 using System; using System.Collections.Generic; ///  /// Non-generic class to produce instances of the generic class, /// optionally using type inference. ///  public static class ProjectionEqualityComparer { ///  /// Creates an instance of ProjectionEqualityComparer using the specified projection. ///  /// Type parameter for the elements to be compared /// Type parameter for the keys to be compared, /// after being projected from the elements /// Projection to use when determining the key of an element /// A comparer which will compare elements by projecting /// each element to its key, and comparing keys public static ProjectionEqualityComparer Create(Func projection) { return new ProjectionEqualityComparer(projection); } ///  /// Creates an instance of ProjectionEqualityComparer using the specified projection. /// The ignored parameter is solely present to aid type inference. ///  /// Type parameter for the elements to be compared /// Type parameter for the keys to be compared, /// after being projected from the elements /// Value is ignored - type may be used by type inference /// Projection to use when determining the key of an element /// A comparer which will compare elements by projecting /// each element to its key, and comparing keys public static ProjectionEqualityComparer Create (TSource ignored, Func projection) { return new ProjectionEqualityComparer(projection); } } ///  /// Class generic in the source only to produce instances of the /// doubly generic class, optionally using type inference. ///  public static class ProjectionEqualityComparer { ///  /// Creates an instance of ProjectionEqualityComparer using the specified projection. ///  /// Type parameter for the keys to be compared, /// after being projected from the elements /// Projection to use when determining the key of an element /// A comparer which will compare elements by projecting each element to its key, /// and comparing keys public static ProjectionEqualityComparer Create(Func projection) { return new ProjectionEqualityComparer(projection); } } ///  /// Comparer which projects each element of the comparison to a key, and then compares /// those keys using the specified (or default) comparer for the key type. ///  /// Type of elements which this comparer /// will be asked to compare /// Type of the key projected /// from the element public class ProjectionEqualityComparer : IEqualityComparer { readonly Func projection; readonly IEqualityComparer comparer; ///  /// Creates a new instance using the specified projection, which must not be null. /// The default comparer for the projected type is used. ///  /// Projection to use during comparisons public ProjectionEqualityComparer(Func projection) : this(projection, null) { } ///  /// Creates a new instance using the specified projection, which must not be null. ///  /// Projection to use during comparisons /// The comparer to use on the keys. May be null, in /// which case the default comparer will be used. public ProjectionEqualityComparer(Func projection, IEqualityComparer comparer) { if (projection == null) { throw new ArgumentNullException("projection"); } this.comparer = comparer ?? EqualityComparer.Default; this.projection = projection; } ///  /// Compares the two specified values for equality by applying the projection /// to each value and then using the equality comparer on the resulting keys. Null /// references are never passed to the projection. ///  public bool Equals(TSource x, TSource y) { if (x == null && y == null) { return true; } if (x == null || y == null) { return false; } return comparer.Equals(projection(x), projection(y)); } ///  /// Produces a hash code for the given value by projecting it and /// then asking the equality comparer to find the hash code of /// the resulting key. ///  public int GetHashCode(TSource obj) { if (obj == null) { throw new ArgumentNullException("obj"); } return comparer.GetHashCode(projection(obj)); } } 

Y aquí hay un uso de muestra:

 var f3 = f1.Except(f2, ProjectionEqualityComparer.Create(a => a.key)); 

aquí hay una clase de ayuda simple que debe hacer lo que quieras

 public class EqualityComparer : IEqualityComparer { public EqualityComparer(Func cmp) { this.cmp = cmp; } public bool Equals(T x, T y) { return cmp(x, y); } public int GetHashCode(T obj) { return obj.GetHashCode(); } public Func cmp { get; set; } } 

puedes usarlo así:

 processed.Union(suburbs, new EqualityComparer((s1, s2) => s1.SuburbId == s2.SuburbId)); 

Me parece que proporcionar ayuda adicional en IEnumerable es una forma más limpia de hacerlo.

Ver: esta pregunta

Entonces podrías tener:

 var f3 = f1.Except( f2, (a, b) => a.key.CompareTo(b.key) ); 

Si define los métodos de extensión adecuadamente

Este proyecto hace algo similar: AnonymousComparer – selector de comparación lambda para Linq , también tiene extensiones para los operadores de consultas estándar LINQ.

¿Por qué no algo como esto?

  public class Comparer : IEqualityComparer { private readonly Func _equalityComparer; public Comparer(Func equalityComparer) { _equalityComparer = equalityComparer; } public bool Equals(T first, T second) { return _equalityComparer(first, second); } public int GetHashCode(T value) { return value.GetHashCode(); } } 

y luego podría hacer, por ejemplo, algo así como (por ejemplo, en el caso de Intersect en IEnumerable ):

 list.Intersect(otherList, new Comparer( (x, y) => x.Property == y.Property)); 

La clase Comparer puede colocarse en un proyecto de utilidades y usarse donde sea que se necesite.

Solo ahora veo la respuesta de Sam Saffron (que es muy similar a esta).

Para juegos pequeños, puede hacer:

 f3 = f1.Where(x1 => f2.All(x2 => x2.key != x1.key)); 

Para juegos grandes, deseará algo más eficiente en la búsqueda como:

 var tmp = new HashSet(f2.Select(f => f.key)); f3 = f1.Where(f => tmp.Add(f.key)); 

Pero, aquí, el Type de clave debe implementar IEqualityComparer (arriba supuse que era una string ). Por lo tanto, esto realmente no responde su pregunta sobre el uso de una lambda en esta situación, pero sí utiliza menos código que algunas de las respuestas que sí lo hacen.

Puede confiar en el optimizador y acortar la segunda solución a:

 f3 = f1.Where(x1 => (new HashSet(f2.Select(x2 => x2.key))).Add(x1.key)); 

pero no hice pruebas para saber si funciona a la misma velocidad. Y ese trazador de líneas de uno puede ser demasiado listo para mantener.

Al igual que las otras respuestas pero más conciso c # 7:

 public class LambdaComparer : IEqualityComparer { private readonly Func lambdaComparer; private readonly Func lambdaHash; public LambdaComparer(Func lambdaComparer) : this(lambdaComparer, o => o.GetHashCode()) {} public LambdaComparer(Func lambdaComparer, Func lambdaHash) { this.lambdaComparer = lambdaComparer; this.lambdaHash = lambdaHash; } public bool Equals(T x, T y) => lambdaComparer is null ? false : lambdaComparer(x, y); public int GetHashCode(T obj) => lambdaHash is null ? 0 : lambdaHash(obj); } 

entonces:

 var a=List { "a", "b" }; var b=List { "a", "*" }; return a.SequenceEquals(b, new LambdaComparer((s1, s2) => s1 is null ? s2 is null : s1 == s2 || s2 == "*");