method how extension extended enum custom create c# c#-3.0 lambda extension-methods

extended - how to add extension method in c#



Distinto() con lambda? (18)

Correcto, entonces tengo una enumerable y deseo obtener valores distintos de ella.

Usando System.Linq , existe un método de extensión llamado Distinct . En el caso simple, se puede utilizar sin parámetros, como:

var distinctValues = myStringList.Distinct();

Bien y bien, pero si tengo una cantidad de objetos para los cuales necesito especificar igualdad, la única sobrecarga disponible es:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

El argumento del comparador de igualdad debe ser una instancia de IEqualityComparer<T> . Puedo hacer esto, por supuesto, pero es algo verboso y, bueno, un poco confuso.

Lo que hubiera esperado es una sobrecarga que tomaría un lambda, digamos Func <T, T, bool>:

var distinctValues = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

¿Alguien sabe si existe alguna extensión de este tipo, o alguna solución equivalente? ¿O me estoy perdiendo algo?

Alternativamente, ¿hay una manera de especificar un IEqualityComparer en línea (avergonzarme)?

Actualizar

Encontré una respuesta de Anders Hejlsberg a una post en un foro de MSDN sobre este tema. Él dice:

El problema con el que se encontrará es que cuando dos objetos se comparan de la misma manera, deben tener el mismo valor de retorno de GetHashCode (o, de lo contrario, la tabla hash utilizada internamente por Distinct no funcionará correctamente). Utilizamos IEqualityComparer porque empaqueta implementaciones compatibles de Equals y GetHashCode en una sola interfaz.

Supongo que tiene sentido..


Algo que he usado que me funcionó bien.

/// <summary> /// A class to wrap the IEqualityComparer interface into matching functions for simple implementation /// </summary> /// <typeparam name="T">The type of object to be compared</typeparam> public class MyIEqualityComparer<T> : IEqualityComparer<T> { /// <summary> /// Create a new comparer based on the given Equals and GetHashCode methods /// </summary> /// <param name="equals">The method to compute equals of two T instances</param> /// <param name="getHashCode">The method to compute a hashcode for a T instance</param> public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode) { if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances"); EqualsMethod = equals; GetHashCodeMethod = getHashCode; } /// <summary> /// Gets the method used to compute equals /// </summary> public Func<T, T, bool> EqualsMethod { get; private set; } /// <summary> /// Gets the method used to compute a hash code /// </summary> public Func<T, int> GetHashCodeMethod { get; private set; } bool IEqualityComparer<T>.Equals(T x, T y) { return EqualsMethod(x, y); } int IEqualityComparer<T>.GetHashCode(T obj) { if (GetHashCodeMethod == null) return obj.GetHashCode(); return GetHashCodeMethod(obj); } }


Aquí hay un método de extensión simple que hace lo que necesito ...

public static class EnumerableExtensions { public static IEnumerable<TKey> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector) { return source.GroupBy(selector).Select(x => x.Key); } }

Es una pena que no hayan incorporado un método distinto como este en el marco, pero oye, ho.


Así es como puedes hacerlo:

public static class Extensions { public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query, Func<T, V> f, Func<IGrouping<V,T>,T> h=null) { if (h==null) h=(x => x.First()); return query.GroupBy(f).Select(h); } }

Este método le permite usarlo especificando un parámetro como .MyDistinct(d => d.Name) , pero también le permite especificar una condición de tener como segundo parámetro, como así:

var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name, x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2")) );

NB Esto también le permitiría especificar otras funciones como, por ejemplo, .LastOrDefault(...) también.

Si desea exponer solo la condición, puede hacerlo aún más simple implementándolo como:

public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query, Func<T, V> f, Func<T,bool> h=null ) { if (h == null) h = (y => true); return query.GroupBy(f).Select(x=>x.FirstOrDefault(h)); }

En este caso, la consulta se vería así:

var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name, y => y.Name.Contains("1") || y.Name.Contains("2") );

NB: Aquí la expresión es más simple, pero tenga en cuenta .MyDistinct2 usa .FirstOrDefault(...) .

Nota: los ejemplos anteriores están utilizando la siguiente clase de demostración

class MyObject { public string Name; public string Code; } private MyObject[] _myObject = { new MyObject() { Name = "Test1", Code = "T"}, new MyObject() { Name = "Test2", Code = "Q"}, new MyObject() { Name = "Test2", Code = "T"}, new MyObject() { Name = "Test5", Code = "Q"} };


El paquete Microsoft System.Interactive tiene una versión de Distinct que toma un selector de clave lambda. Esta es efectivamente la misma solución que la de Jon Skeet, pero puede ser útil para que la gente lo sepa y revise el resto de la biblioteca.


Esto hará lo que quieras pero no sé sobre el rendimiento:

var distinctValues = from cust in myCustomerList group cust by cust.CustomerId into gcust select gcust.First();

Al menos no es verboso.


Me parece que quieres DistinctBy de MoreLINQ . A continuación, puede escribir:

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

Aquí hay una versión reducida de DistinctBy (sin comprobación de nulidad y sin opción para especificar su propio comparador de claves):

public static IEnumerable<TSource> DistinctBy<TSource, TKey> (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) { HashSet<TKey> knownKeys = new HashSet<TKey>(); foreach (TSource element in source) { if (knownKeys.Add(keySelector(element))) { yield return element; } } }


No, no hay tal sobrecarga del método de extensión para esto. He encontrado esto frustrante en el pasado y, como tal, por lo general escribo una clase de ayuda para resolver este problema. El objetivo es convertir un Func<T,T,bool> a IEqualityComparer<T,T> .

Ejemplo

public class EqualityFactory { private sealed class Impl<T> : IEqualityComparer<T,T> { private Func<T,T,bool> m_del; private IEqualityComparer<T> m_comp; public Impl(Func<T,T,bool> del) { m_del = del; m_comp = EqualityComparer<T>.Default; } public bool Equals(T left, T right) { return m_del(left, right); } public int GetHashCode(T value) { return m_comp.GetHashCode(value); } } public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) { return new Impl<T>(del); } }

Esto te permite escribir lo siguiente

var distinctValues = myCustomerList .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));


Puedes usar InlineComparer

public class InlineComparer<T> : IEqualityComparer<T> { //private readonly Func<T, T, bool> equalsMethod; //private readonly Func<T, int> getHashCodeMethod; public Func<T, T, bool> EqualsMethod { get; private set; } public Func<T, int> GetHashCodeMethod { get; private set; } public InlineComparer(Func<T, T, bool> equals, Func<T, int> hashCode) { if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all InlineComparer instances"); EqualsMethod = equals; GetHashCodeMethod = hashCode; } public bool Equals(T x, T y) { return EqualsMethod(x, y); } public int GetHashCode(T obj) { if (GetHashCodeMethod == null) return obj.GetHashCode(); return GetHashCodeMethod(obj); } }

Muestra de uso

var comparer = new InlineComparer<DetalleLog>((i1, i2) => i1.PeticionEV == i2.PeticionEV && i1.Etiqueta == i2.Etiqueta, i => i.PeticionEV.GetHashCode() + i.Etiqueta.GetHashCode()); var peticionesEV = listaLogs.Distinct(comparer).ToList(); Assert.IsNotNull(peticionesEV); Assert.AreNotEqual(0, peticionesEV.Count);

Fuente: https://.com/a/5969691/206730
Usando IEqualityComparer para Union
¿Puedo especificar mi comparador de tipos explícitos en línea?


Puedes usar LambdaEqualityComparer:

var distinctValues = myCustomerList.Distinct(new LambdaEqualityComparer<OurType>((c1, c2) => c1.CustomerId == c2.CustomerId)); public class LambdaEqualityComparer<T> : IEqualityComparer<T> { public LambdaEqualityComparer(Func<T, T, bool> equalsFunction) { _equalsFunction = equalsFunction; } public bool Equals(T x, T y) { return _equalsFunction(x, y); } public int GetHashCode(T obj) { return obj.GetHashCode(); } private readonly Func<T, T, bool> _equalsFunction; }


Si Distinct() no produce resultados únicos, pruebe este:

var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID); ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);


Solución abreviada

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());


Supongo que tiene un IEnumerable, y en su delegado de ejemplo, ¿le gustaría que c1 y c2 se refieran a dos elementos en esta lista?

Creo que podrías lograr esto con una combinación propia var distinctResults = from c1 en myList join c2 en myList on


Todas las soluciones que he visto aquí se basan en la selección de un campo ya comparable. Sin embargo, si uno necesita comparar de una manera diferente, esta solución aquí parece funcionar, para algo como:

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()


Toma otra manera:

var distinctValues = myCustomerList. Select(x => x._myCaustomerProperty).Distinct();

La secuencia devuelve distintos elementos comparándolos por la propiedad ''_myCaustomerProperty''.


Una forma difícil de hacer esto es usar Aggregate() extensión Aggregate() , usando un diccionario como acumulador con los valores de propiedad clave como claves:

var customers = new List<Customer>(); var distincts = customers.Aggregate(new Dictionary<int, Customer>(), (d, e) => { d[e.CustomerId] = e; return d; }, d => d.Values);

Y una solución de estilo GroupBy está utilizando ToLookup() :

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());


IEnumerable extensión lambda:

public static class ListExtensions { public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, int> hashCode) { Dictionary<int, T> hashCodeDic = new Dictionary<int, T>(); list.ToList().ForEach(t => { var key = hashCode(t); if (!hashCodeDic.ContainsKey(key)) hashCodeDic.Add(key, t); }); return hashCodeDic.Select(kvp => kvp.Value); } }

Uso:

class Employee { public string Name { get; set; } public int EmployeeID { get; set; } } //Add 5 employees to List List<Employee> lst = new List<Employee>(); Employee e = new Employee { Name = "Shantanu", EmployeeID = 123456 }; lst.Add(e); lst.Add(e); Employee e1 = new Employee { Name = "Adam Warren", EmployeeID = 823456 }; lst.Add(e1); //Add a space in the Name Employee e2 = new Employee { Name = "Adam Warren", EmployeeID = 823456 }; lst.Add(e2); //Name is different case Employee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 }; lst.Add(e3); //Distinct (without IEqalityComparer<T>) - Returns 4 employees var lstDistinct1 = lst.Distinct(); //Lambda Extension - Return 2 employees var lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode());


Para envolver las cosas . Creo que la mayoría de las personas que vinieron aquí como yo quieren la solución más simple posible sin usar ninguna biblioteca y con el mejor rendimiento posible.

(El grupo aceptado por método para mí, creo que es una exageración en términos de rendimiento).

Aquí hay un método de extensión simple que usa la interfaz IEqualityComparer que también funciona para valores nulos.

Uso:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();

Código del método de extensión

public static class LinqExtensions { public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property) { GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property); return items.Distinct(comparer); } } public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T> { private Func<T, TKey> expr { get; set; } public GeneralPropertyComparer (Func<T, TKey> expr) { this.expr = expr; } public bool Equals(T left, T right) { var leftProp = expr.Invoke(left); var rightProp = expr.Invoke(right); if (leftProp == null && rightProp == null) return true; else if (leftProp == null ^ rightProp == null) return false; else return leftProp.Equals(rightProp); } public int GetHashCode(T obj) { var prop = expr.Invoke(obj); return (prop==null)? 0:prop.GetHashCode(); } }


IEnumerable<Customer> filteredList = originalList .GroupBy(customer => customer.CustomerId) .Select(group => group.First());