2011-01-05 7 views
42

ho una distinta dichiarazione LINQ() che utilizza il mio operatore di confronto personalizzato, in questo modo:Utilizzare un delegato per il confronto di uguaglianza per di LINQ distinte()

class MyComparer<T> : IEqualityComparer<T> where T : MyType 
{ 
    public bool Equals(T x, T y) 
    { 
     return x.Id.Equals(y.Id); 
    } 

    public int GetHashCode(T obj) 
    { 
     return obj.Id.GetHashCode(); 
    } 
} 

... 

var distincts = bundle.GetAllThings.Distinct(new MyComparer<MySubType>()); 

Questo è tutto bene e dandy e lavora come ho volere. Per curiosità, devo definire il mio Comparatore o posso sostituirlo con un delegato? Ho pensato che dovrei essere in grado di fare qualcosa del genere:

var distincts = bundle.GetAllThings.Distinct((a,b) => a.Id == b.Id); 

Ma questo non viene compilato. C'è un trucco pulito?

+0

Dovresti avere un 'ReferenceEquals' controlli contro nulla su' 'x' e y' nel vostro' implementazione Equals'. – nicodemus13

risposta

98

Distinto accetta un IEqualityComparer come secondo argomento, quindi è necessario unCompetitore IEquality. Non è troppo difficile da fare un generico che prenderà un delegato, però. Naturalmente, questo probabilmente è già stato implementato in alcuni punti, come ad esempio MoreLINQ suggerito in una delle altre risposte.

Si potrebbe implementare qualcosa di simile:

public static class Compare 
{ 
    public static IEnumerable<T> DistinctBy<T, TIdentity>(this IEnumerable<T> source, Func<T, TIdentity> identitySelector) 
    { 
     return source.Distinct(Compare.By(identitySelector)); 
    } 

    public static IEqualityComparer<TSource> By<TSource, TIdentity>(Func<TSource, TIdentity> identitySelector) 
    { 
     return new DelegateComparer<TSource, TIdentity>(identitySelector); 
    } 

    private class DelegateComparer<T, TIdentity> : IEqualityComparer<T> 
    { 
     private readonly Func<T, TIdentity> identitySelector; 

     public DelegateComparer(Func<T, TIdentity> identitySelector) 
     { 
      this.identitySelector = identitySelector; 
     } 

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

     public int GetHashCode(T obj) 
     { 
      return identitySelector(obj).GetHashCode(); 
     } 
    } 
} 

che vi dà la sintassi:

source.DistinctBy(a => a.Id); 

O, se si sente che è più chiaro in questo modo:

source.Distinct(Compare.By(a => a.Id)); 
+2

Fatto ciò, è anche possibile scrivere il proprio metodo di estensione che accetta un delegato. –

+0

@David, esattamente :-) – driis

+0

Fantastico. Mi mancava questa funzionalità fin dall'inizio di LINQ. – Konamiman

10

È un peccato che lo Distinct non abbia un tale sovraccarico, quindi quello che hai è una buona opzione.

Con MoreLinq, è possibile utilizzare l'operatore DistinctBy.

var distincts = bundle.GetAllThings.DistinctBy(a => a.Id); 

si potrebbe anche prendere in considerazione la scrittura un generico ProjectionEqualityComparer che può trasformare il delegato appropriata in un IEqualityComparer<T> implementazione, come quello elencato here.

2

Questo link mostra come creare il metodo di estensione per poter usare Distinct nel modo che hai dato. Avrai bisogno di scrivere due metodi di estensione Distinct e uno IEqualityComparer.

Ecco il codice, dal sito:

public static class Extensions 
    { 
     public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, Func<T, T, bool> comparer) 
     {   
      return source.Distinct(new DelegateComparer<T>(comparer)); 
     } 

     public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, Func<T, T, bool> comparer, Func<T,int> hashMethod) 
     { 
      return source.Distinct(new DelegateComparer<T>(comparer,hashMethod)); 
     } 
    } 

    public class DelegateComparer<T> : IEqualityComparer<T> 
    { 
     private Func<T, T, bool> _equals; 
     private Func<T,int> _getHashCode; 

     public DelegateComparer(Func<T, T, bool> equals) 
     { 
      this._equals = equals; 
     } 

     public DelegateComparer(Func<T, T, bool> equals, Func<T,int> getHashCode) 
     { 
      this._equals = equals; 
      this._getHashCode = getHashCode; 
     } 

     public bool Equals(T a, T b) 
     { 
      return _equals(a, b); 
     } 

     public int GetHashCode(T a) 
     { 
      if (_getHashCode != null)  
       return _getHashCode(a);  
      else 
       return a.GetHashCode(); 
     } 
    } 
+4

Il problema con questo codice è che non segue le regole per GetHashCode ed Equals (vedere msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx). Ogni volta che si utilizza il primo sovraccarico su una classe personalizzata, è molto probabile che si ottengano risultati errati. GetHashCode * deve * restituire lo stesso valore per due oggetti quando Equals restituisce true. –