c# - property - linq select distinct object from list




Distinto() con lambda? (12)

È possibile utilizzare 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);
    }
}

Campione di utilizzo :

  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);

Fonte: https://stackoverflow.com/a/5969691/206730
Utilizzo di IEqualityComparer per Union
Posso specificare il mio comparatore di tipo esplicito in linea?

Giusto, quindi ho un numero enumerabile e desidero ottenere valori distinti da esso.

Usando System.Linq , c'è ovviamente un metodo di estensione chiamato Distinct . Nel caso semplice, può essere utilizzato senza parametri, come:

var distinctValues = myStringList.Distinct();

Bene e bene, ma se ho una quantità enumerabile di oggetti per i quali ho bisogno di specificare l'uguaglianza, l'unico sovraccarico disponibile è:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

L'argomento di confronto dell'eguaglianza deve essere un'istanza di IEqualityComparer<T> . Posso farlo, ovviamente, ma è piuttosto prolisso e, beh, buono.

Quello che mi sarei aspettato è un sovraccarico che richiederebbe un lambda, ad esempio Func <T, T, bool>:

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

Qualcuno sa se esiste qualche estensione, o qualche soluzione alternativa? Oppure mi sfugge qualcosa?

In alternativa, c'è un modo per specificare un IEqualityComparer in linea (imbarazzami)?

Aggiornare

Ho trovato una risposta di Anders Hejlsberg a un post in un forum MSDN su questo argomento. Lui dice:

Il problema che si verificherà è che quando due oggetti sono uguali devono avere lo stesso valore di ritorno GetHashCode (oppure la tabella hash utilizzata internamente da Distinct non funzionerà correttamente). Utilizziamo IEqualityComparer perché impacchetta le implementazioni compatibili di Equals e GetHashCode in un'unica interfaccia.

Suppongo abbia senso..


È possibile utilizzare 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;
    }

Ecco un semplice metodo di estensione che fa ciò di cui ho bisogno ...

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);
    }
}

È un peccato che non abbiano infuso un metodo distinto come questo nel quadro, ma hey ho.


Estensione lambda IEnumerable :

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()); 

Mi sembra che tu voglia DistinctBy da MoreLINQ . Puoi quindi scrivere:

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

Ecco una versione ridotta di DistinctBy (nessun controllo di nullità e nessuna opzione per specificare il proprio comparatore di chiavi):

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, non esiste un sovraccarico del metodo di estensione per questo. Ho trovato questo frustrante me stesso in passato e come tale di solito scrivo una classe di supporto per affrontare questo problema. L'obiettivo è convertire un Func<T,T,bool> in IEqualityComparer<T,T> .

Esempio

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);
  }
}

Questo ti permette di scrivere quanto segue

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

Qualcosa che ho usato che ha funzionato bene per me.

/// <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);
    }
}

Questo farà ciò che vuoi ma non so delle prestazioni:

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

Almeno non è prolisso.


Soluzione stenografica

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

Suppongo che tu abbia un oggetto IEnumerable e nel tuo esempio delegato desideri che c1 e c2 si riferiscano a due elementi in questo elenco?

Credo che potresti raggiungere questo obiettivo con un self join var distinctResults = da c1 in myList join c2 in myList su


Un modo complicato per farlo è usare l'estensione Aggregate() , usando un dizionario come accumulatore con i valori della proprietà chiave come chiavi:

var customers = new List<Customer>();

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

E una soluzione in stile GroupBy utilizza ToLookup() :

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

Per avvolgere le cose Penso che la maggior parte delle persone che sono venute qui come me vogliono la soluzione più semplice possibile senza utilizzare alcuna libreria e con le migliori prestazioni possibili.

(Il gruppo accettato dal metodo per me è un eccesso di prestazioni in termini di prestazioni.)

Ecco un semplice metodo di estensione che utilizza l'interfaccia IEqualityComparer che funziona anche con valori null.

Uso:

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

Codice del metodo di estensione

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();
    }
}






extension-methods