Distinct () de LINQ en una propiedad en particular

Estoy jugando con LINQ para aprender sobre él, pero no sé cómo usarlo. Distinct cuando no tengo una lista simple (una lista simple de números enteros es bastante fácil de hacer, esta no es la pregunta). Que si quiero usar Distinto en una lista de un objeto en . or más, propiedades del objeto?

Ejemplo: si un objeto es Person, con Propiedad Id. ¿Cómo puedo obtener todas las personas y usarlas? Distinct en ellos con la propiedad Id del objeto?

Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"

¿Cómo puedo conseguir solo Person1 y Person3? ¿Es eso posible?

Si no es posible con LINQ, ¿cuál sería la mejor manera de tener una lista de Person dependiendo de algunas de sus propiedades en .NET 3.5?

preguntado el 28 de enero de 09 a las 18:01

20 Respuestas

EDITAR: Esto ahora es parte de MásLINQ.

Lo que necesita es un "distintivo" eficaz. No creo que sea parte de LINQ tal como está, aunque es bastante fácil de escribir:

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

Entonces, para encontrar los valores distintos usando solo el Id propiedad, puede usar:

var query = people.DistinctBy(p => p.Id);

Y para usar múltiples propiedades, puede usar tipos anónimos, que implementan la igualdad de manera apropiada:

var query = people.DistinctBy(p => new { p.Id, p.Name });

No probado, pero debería funcionar (y ahora al menos se compila).

Sin embargo, asume el comparador predeterminado para las claves; si desea pasar un comparador de igualdad, simplemente páselo al HashSet constructor.

respondido 04 nov., 15:12

@ ashes999: No estoy seguro de a qué te refieres. El código está presente en la respuesta. y en la biblioteca, dependiendo de si está dispuesto a asumir una dependencia. - jon skeet

@JonSkeet que usé GroupBy ya que es más simple: no hay variables adicionales (hashset) para declarar. Sin embargo, me gusta la extensibilidad de poder pasar un comparador allí. - cenizas999

@ ashes999: Si solo está haciendo esto en un solo lugar, entonces seguro, usando GroupBy es más simple. Si lo necesita en más de un lugar, es mucho más limpio (IMO) encapsular la intención. - jon skeet

@MatthewWhited: Dado que no se menciona IQueryable<T> aquí, no veo qué tan relevante es. Estoy de acuerdo en que esto no sería adecuado para EF, etc., pero dentro de LINQ to Objects creo que es más, adecuado que GroupBy. El contexto de la pregunta siempre es importante. - jon skeet

El proyecto se movió en github, aquí está el código de DistinctBy: github.com/morelinq/MoreLINQ/blob/master/MoreLinq/DistinctBy.cs - destino01

¿Qué pasa si quiero obtener una lista distinta basada en . or más, propiedades?

¡Sencillo! Desea agruparlos y elegir un ganador del grupo.

List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.First())
  .ToList();

Si desea definir grupos en varias propiedades, así es como:

List<Person> distinctPeople = allPeople
  .GroupBy(p => new {p.PersonId, p.FavoriteColor} )
  .Select(g => g.First())
  .ToList();

Respondido el 14 de enero de 20 a las 19:01

@ErenErsonmez seguro. Con mi código publicado, si se desea la ejecución diferida, deje de llamar a ToList. - Amy B

¡Muy buena respuesta! Realmente me ayudó en Linq-to-Entities conducido desde una vista SQL donde no pude modificar la vista. Necesitaba usar FirstOrDefault () en lugar de First (); todo está bien. - Alex KeySmith

Lo probé y debería cambiar a Select (g => g.FirstOrDefault ()) - user585440

@ChocapicSz Nop. Ambas cosas Single() y SingleOrDefault() cada lanzamiento cuando la fuente tiene más de un elemento. En esta operación, esperamos la posibilidad de que cada grupo tenga más de un artículo. Para esa materia, First() se prefiere sobre FirstOrDefault() porque cada grupo debe tener al menos un miembro ... a menos que esté usando EntityFramework, que no puede darse cuenta de que cada grupo tiene al menos un miembro y exige FirstOrDefault(). - Amy B

Parece que actualmente no se admite en EF Core, incluso si se usa FirstOrDefault() github.com/dotnet/efcore/issues/12088 Estoy en 3.1 y obtengo errores de "no puedo traducir". - Colin M. Barrett

Uso:

List<Person> pList = new List<Person>();
/* Fill list */

var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());

El where le ayuda a filtrar las entradas (podría ser más complejo) y el groupby y select realizar la función distinta.

Respondido el 31 de diciembre de 18 a las 02:12

Perfecto y funciona sin extender Linq o usar otra dependencia. - davidscherer

Una gran respuesta aquí. Gracias - ciencia

También puede usar la sintaxis de consulta si desea que se vea como LINQ:

var uniquePeople = from p in people
                   group p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever}
                   into mygroup
                   select mygroup.FirstOrDefault();

Respondido el 20 de enero de 17 a las 19:01

Mmmm, mis pensamientos son que tanto la sintaxis de consulta como la sintaxis fluida de la API son tan LINQ como entre sí y su preferencia justa sobre las que usa la gente. Yo mismo prefiero la API fluida, así que lo consideraría más LINK-Like, pero supongo que eso es subjetivo. Max Carroll

LINQ-Like no tiene nada que ver con las preferencias, ser "LINQ-like" tiene que ver con verse como un lenguaje de consulta diferente incrustado en C #, prefiero la interfaz fluida, proveniente de secuencias de Java, pero NO es LINQ-Like. - Ryan la lixiviación

¡¡Excelente!! ¡Eres mi héroe! - farzin kanzi

Creo que es suficiente:

list.Select(s => s.MyField).Distinct();

Respondido el 23 de enero de 15 a las 14:01

¿Qué pasa si necesita recuperar su objeto completo, no solo ese campo en particular? - Festim Cahaní

¿Qué objeto exactamente de los varios objetos que tienen el mismo valor de propiedad? - donRumatta

Primero agrupe la solución por sus campos y luego seleccione el elemento primero o predeterminado.

    List<Person> distinctPeople = allPeople
   .GroupBy(p => p.PersonId)
   .Select(g => g.FirstOrDefault())
   .ToList();

Respondido 13 Jul 17, 09:07

Puedes hacer esto con el estándar Linq.ToLookup(). Esto creará una colección de valores para cada clave única. Simplemente seleccione el primer elemento de la colección

Persons.ToLookup(p => p.Id).Select(coll => coll.First());

Respondido el 16 de enero de 16 a las 19:01

El siguiente código es funcionalmente equivalente a La respuesta de Jon Skeet.

Probado en .NET 4.5, debería funcionar en cualquier versión anterior de LINQ.

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
  this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
  HashSet<TKey> seenKeys = new HashSet<TKey>();
  return source.Where(element => seenKeys.Add(keySelector(element)));
}

Incidentalmente, echa un vistazo La última versión de Jon Skeet de DistinctBy.cs en Google Code.

contestado el 23 de mayo de 17 a las 13:05

Esto me dio una "secuencia no tiene error de valores", pero la respuesta de Skeet produjo el resultado correcto. - ¿Qué sería genial?

Escribí un artículo que explica cómo extender la función Distinct para que pueda hacer lo siguiente:

var people = new List<Person>();

people.Add(new Person(1, "a", "b"));
people.Add(new Person(2, "c", "d"));
people.Add(new Person(1, "a", "b"));

foreach (var person in people.Distinct(p => p.ID))
    // Do stuff with unique list here.

Aquí está el artículo (ahora en el archivo web): Ampliación de LINQ: especificación de una propiedad en la función distinta

Respondido el 11 de Septiembre de 20 a las 20:09

Tu artículo tiene un error, debería haber un después de Distinct: public static IEnumerable Distinto (esto ... Además, no parece que funcione (bien) en más de una propiedad, es decir, una combinación de nombres y apellidos. row1

+1, un error menor no es una razón suficiente para votar en contra, que tan tonto, se llama un error tipográfico a menudo. ¡Y todavía tengo que ver una función genérica que funcione para cualquier número de propiedad! Espero que el votante negativo también haya rechazado todas las demás respuestas de este hilo. Pero oye, ¿cuál es este segundo tipo de objeto? ¡Me opongo! - nawfal

Tu enlace está roto Tom pelusa

Personalmente utilizo la siguiente clase:

public class LambdaEqualityComparer<TSource, TDest> : 
    IEqualityComparer<TSource>
{
    private Func<TSource, TDest> _selector;

    public LambdaEqualityComparer(Func<TSource, TDest> selector)
    {
        _selector = selector;
    }

    public bool Equals(TSource obj, TSource other)
    {
        return _selector(obj).Equals(_selector(other));
    }

    public int GetHashCode(TSource obj)
    {
        return _selector(obj).GetHashCode();
    }
}

Luego, un método de extensión:

public static IEnumerable<TSource> Distinct<TSource, TCompare>(
    this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
    return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}

Finalmente, el uso previsto:

var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);

La ventaja que encontré al usar este enfoque es la reutilización de LambdaEqualityComparer clase para otros métodos que aceptan una IEqualityComparer. (Oh, y dejo el yield cosas a la implementación LINQ original ...)

Respondido 30 Oct 15, 18:10

Puede usar DistinctBy () para obtener registros Distinct por una propiedad de objeto. Simplemente agregue la siguiente declaración antes de usarlo:

utilizando Microsoft.Ajax.Utilities;

y luego úselo de la siguiente manera:

var listToReturn = responseList.DistinctBy(x => x.Index).ToList();

donde 'Índice' es la propiedad en la que quiero que los datos sean distintos.

respondido 27 mar '19, 06:03

Puedes hacerlo (aunque no a la velocidad de un rayo) así:

people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));

Es decir, "seleccione todas las personas en las que no haya otra persona diferente en la lista con el mismo ID".

Tenga en cuenta que, en su ejemplo, eso solo seleccionaría a la persona 3. No estoy seguro de cómo decir cuál quiere, de los dos anteriores.

Respondido el 28 de enero de 09 a las 20:01

En caso de que necesite un método distinto en varias propiedades, puede consultar mi Extensiones poderosas Biblioteca. Actualmente está en una etapa muy joven, pero ya puedes usar métodos como Distinct, Union, Intersect, Excepto en cualquier cantidad de propiedades;

Así es como lo usas:

using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);

Respondido el 16 de enero de 16 a las 18:01

Cuando nos enfrentamos a una tarea de este tipo en nuestro proyecto, definimos una pequeña API para componer comparadores.

Entonces, el caso de uso fue así:

var wordComparer = KeyEqualityComparer.Null<Word>().
    ThenBy(item => item.Text).
    ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);

Y la API en sí se ve así:

using System;
using System.Collections;
using System.Collections.Generic;

public static class KeyEqualityComparer
{
    public static IEqualityComparer<T> Null<T>()
    {
        return null;
    }

    public static IEqualityComparer<T> EqualityComparerBy<T, K>(
        this IEnumerable<T> source,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc);
    }

    public static KeyEqualityComparer<T, K> ThenBy<T, K>(
        this IEqualityComparer<T> equalityComparer,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc, equalityComparer);
    }
}

public struct KeyEqualityComparer<T, K>: IEqualityComparer<T>
{
    public KeyEqualityComparer(
        Func<T, K> keyFunc,
        IEqualityComparer<T> equalityComparer = null)
    {
        KeyFunc = keyFunc;
        EqualityComparer = equalityComparer;
    }

    public bool Equals(T x, T y)
    {
        return ((EqualityComparer == null) || EqualityComparer.Equals(x, y)) &&
                EqualityComparer<K>.Default.Equals(KeyFunc(x), KeyFunc(y));
    }

    public int GetHashCode(T obj)
    {
        var hash = EqualityComparer<K>.Default.GetHashCode(KeyFunc(obj));

        if (EqualityComparer != null)
        {
            var hash2 = EqualityComparer.GetHashCode(obj);

            hash ^= (hash2 << 5) + hash2;
        }

        return hash;
    }

    public readonly Func<T, K> KeyFunc;
    public readonly IEqualityComparer<T> EqualityComparer;
}

Más detalles están en nuestro sitio: IEqualityComparer en LINQ.

Respondido el 16 de enero de 16 a las 18:01

Si no desea agregar la biblioteca MoreLinq a su proyecto solo para obtener la DistinctBy funcionalidad, entonces puede obtener el mismo resultado final utilizando la sobrecarga de Linq Distinct método que toma en un IEqualityComparer argumento.

Empiece por crear una clase de comparación de igualdad personalizada genérica que usa la sintaxis lambda para realizar una comparación personalizada de dos instancias de una clase genérica:

public class CustomEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> _comparison;
    Func<T, int> _hashCodeFactory;

    public CustomEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCodeFactory)
    {
        _comparison = comparison;
        _hashCodeFactory = hashCodeFactory;
    }

    public bool Equals(T x, T y)
    {
        return _comparison(x, y);
    }

    public int GetHashCode(T obj)
    {
        return _hashCodeFactory(obj);
    }
}

Luego, en su código principal, lo usa así:

Func<Person, Person, bool> areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id);

Func<Person, int> getHashCode = (p) => p.Id.GetHashCode();

var query = people.Distinct(new CustomEqualityComparer<Person>(areEqual, getHashCode));

¡Voila! :)

Lo anterior asume lo siguiente:

  • Propiedad Person.Id es de tipo int
  • El people la colección no contiene ningún elemento nulo

Si la colección podría contener nulos, simplemente vuelva a escribir las lambdas para verificar si hay nulos, por ejemplo:

Func<Person, Person, bool> areEqual = (p1, p2) => 
{
    return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};

EDITAR

Este enfoque es similar al de la respuesta de Vladimir Nesterovsky, pero más simple.

También es similar al de la respuesta de Joel, pero permite una lógica de comparación compleja que involucra múltiples propiedades.

Sin embargo, si sus objetos solo pueden diferir en Id luego otro usuario dio la respuesta correcta de que todo lo que necesita hacer es anular las implementaciones predeterminadas de GetHashCode() y Equals() en tu Person class y luego simplemente use el Distinct() método de Linq para filtrar cualquier duplicado.

Respondido 22 ago 16, 19:08

Quiero obtener solo elementos únicos en dictonary. ¿Me pueden ayudar? Estoy usando este código Si TempDT no es nada, entonces m_ConcurrentScriptDictionary = TempDT.AsEnumerable.ToDictionary (Función (x) x.SafeField (fldClusterId, NULL_ID_VALUE), Función (y) y.SafeField (fldParamValue11, NULL_ID_VALUE)) - RSB

La mejor manera de hacer esto que será compatible con otras versiones de .NET es anular Equals y GetHash para manejar esto (vea la pregunta de Stack Overflow Este código devuelve valores distintos. Sin embargo, lo que quiero es devolver una colección fuertemente tipada en lugar de un tipo anónimo), pero si necesita algo genérico en todo su código, las soluciones de este artículo son excelentes.

contestado el 23 de mayo de 17 a las 13:05

Anular Es igual a (objeto obj) y GetHashCode () métodos:

class Person
{
    public int Id { get; set; }
    public int Name { get; set; }

    public override bool Equals(object obj)
    {
        return ((Person)obj).Id == Id;
        // or: 
        // var o = (Person)obj;
        // return o.Id == Id && o.Name == Name;
    }
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

y luego solo llama:

List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();

Respondido el 27 de Septiembre de 18 a las 21:09

Sin embargo, GetHashCode () debería ser más avanzado (para contar también el Nombre), esta respuesta es probablemente la mejor en mi opinión. En realidad, para archivar la lógica de destino, no es necesario anular GetHashCode (), Equals () es suficiente, pero si necesitamos rendimiento, tenemos que anularlo. Para todos los algoritmos de comparación, primero verifique el hash y, si son iguales, llame a Equals (). - Oleg Skripniak

Además, en Equals () la primera línea debería ser "if (! (Obj is Person)) return false". Pero la mejor práctica es usar un objeto separado convertido a un tipo, como "var o = obj como Persona; if (o == null) return false;" luego verifique la igualdad con o sin casting - Oleg Skripniak

Reemplazar iguales de esta manera no es una buena idea, ya que podría tener consecuencias no deseadas para otros programadores que esperan que la Igualdad de la persona se determine en más de una propiedad. - B2K

List<Person>lst=new List<Person>
        var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();

contestado el 16 de mayo de 16 a las 11:05

¿Quisiste decir? Select() new Person en lugar de new Player? El hecho de que esté ordenando por ID no informa de alguna manera Distinct() utilizar esa propiedad para determinar la unicidad, sin embargo, esto no funcionará. - Lance U. Matthews

Debería poder anular Equals en persona para hacer Equals en Person.id. Esto debería resultar en el comportamiento que busca.

Respondido el 28 de enero de 09 a las 20:01

No recomendaría este enfoque. Si bien podría funcionar en este caso específico, es simplemente una mala práctica. ¿Qué pasa si quiere diferenciarse por una propiedad diferente en otro lugar? Seguro que no puede anular Equals dos veces, ¿verdad? :-) Aparte de eso, es fundamentalmente incorrecto anular iguales para este propósito, ya que está destinado a decir si dos objetos son iguales o no. Si la condición de las clases para la igualdad cambia por cualquier motivo, seguro que te quemarás los dedos ... - Alan

Inténtelo con el siguiente código.

var Item = GetAll().GroupBy(x => x .Id).ToList();

Respondido 16 Jul 18, 08:07

Una respuesta breve es bienvenida, sin embargo, no proporcionará mucho valor a los últimos usuarios que están tratando de entender qué está sucediendo detrás del problema. Dedique algo de tiempo a explicar cuál es el problema real que causa el problema y cómo resolverlo. Gracias ~ - escuchar

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.