47

Mi è capitato di vedere un codice in cui questo tipo ha passato un'espressione lambda a ArrayList.Sort (IComparer qui) o IEnumerable.SequenceEqual (IEnumerable list, IEqualityComparer qui) in cui era previsto un compiler o un complemento IEquality.Passare un'espressione lambda al posto di IComparer o IEqualityComparer o qualsiasi interfaccia a metodo singolo?

Non posso essere sicuro se l'ho visto, o sto solo sognando. E non riesco a trovare un'estensione su nessuna di queste raccolte che accetta un Func <> o un delegato nelle loro firme di metodo.

Esiste un tale metodo di sovraccarico/estensione? Oppure, se no, è possibile andare in giro in questo modo e passare un algoritmo (leggi delegato) dove è prevista un'interfaccia a metodo singolo?

Aggiornamento Grazie a tutti. È quello che pensavo. Devo aver sognato. So come scrivere una conversione. Non ero sicuro di aver visto qualcosa del genere o solo di averlo visto.

Ancora un altro aggiornamento Guarda, qui, ho trovato una tale istanza. Dopo tutto non stavo sognando. Guarda what this guy is doing here. Cosa dà?

E questo è un altro aggiornamento: Ok, ho capito. Il ragazzo sta usando il sovraccarico Comparison<T>. Bello. Bello, ma totalmente incline a ingannarti. Bello, però. Grazie.

+1

possibile duplicato di [Avvolgere un delegato in un IEqualityComparer] (http://stackoverflow.com/questions/98033/wrap-a-delegate-in-an-iequalitycomparer) – nawfal

+0

@nawfal: Questa è una domanda diversa. Sono in qualche modo imparentati ma ancora diversi. Questa è una domanda molto bella, però. Grazie per la condivisione. L'ho trovato molto interessante. :-) –

+0

Oh sì, ora vedo, ma molto vicino: P Ho ritirato il voto ravvicinato, ma terrò il commento in modo che gli altri visitatori se ne accorgano. Una cosa, accetti gentilmente le risposte. Penso che la risposta votata in alto risponda alla tua domanda. – nawfal

risposta

3

Io voto per la teoria del sogno.

Non è possibile passare una funzione in cui è previsto un oggetto: le derivate di System.Delegate (che è ciò che sono lambdas) non implementano tali interfacce.

Quello che probabilmente avete visto è un uso del delegato Converter<TInput, TOutput>, che può essere modellato da un lambda. Array.ConvertAll utilizza un'istanza di questo delegato.

10

È possibile fornire un lambda per un metodo Array.Sort, in quanto richiede un metodo che accetta due oggetti di tipo T e restituisce un numero intero. Come tale, è possibile fornire un lambda della seguente definizione (a, b) => a.CompareTo(b). Un esempio per fare un ordine decrescente di un array di interi:

int[] array = { 1, 8, 19, 4 }; 

// descending sort 
Array.Sort(array, (a, b) => -1 * a.CompareTo(b)); 
+0

Puoi spiegarlo ulteriormente? Si tratta di un comportamento univoco dell'interfaccia IComparer o funzionerà su qualsiasi interfaccia che abbia solo un singolo metodo? – StriplingWarrior

+6

@Stripling, credo che questo stia effettivamente utilizzando il sovraccarico che accetta un 'Confronto ', poiché 'Confronto' è un delegato che accetta i due parametri e restituisce il numero intero. Come tale, fornire un lambda valido si qualifica per questo sovraccarico. –

2

Questi metodi non hanno sovraccarichi che accettano un delegato al posto di un'interfaccia, ma:

  • normalmente è possibile restituire una sorta semplice chiave attraverso il delegato si passa a Enumerable.OrderBy
  • Allo stesso modo, si potrebbe chiamare Enumerable.Select prima di chiamare Enumerable.SequenceEqual
  • dovrebbe essere semplice per scrivere un wrapper che implementa IEqualityComparer<T> in termini di Func<T, T, bool>
  • F # consente di implementare questo tipo di interfaccia in termini di una lambda :)
3

Non è possibile passare direttamente però si potrebbe fare in modo attraverso la definizione di una classe LambdaComparer che eccettua un Func<T,T,int> e poi lo usa nel suo CompareTo.

Non è abbastanza conciso ma potresti renderlo più breve attraverso alcuni metodi di estensione creativa su Func.

5
public class Comparer2<T, TKey> : IComparer<T>, IEqualityComparer<T> 
{ 
    private readonly Expression<Func<T, TKey>> _KeyExpr; 
    private readonly Func<T, TKey> _CompiledFunc 
    // Constructor 
    public Comparer2(Expression<Func<T, TKey>> getKey) 
    { 
     _KeyExpr = getKey; 
     _CompiledFunc = _KeyExpr.Compile(); 
    } 

    public int Compare(T obj1, T obj2) 
    { 
     return Comparer<TKey>.Default.Compare(_CompiledFunc(obj1), _CompiledFunc(obj2)); 
    } 

    public bool Equals(T obj1, T obj2) 
    { 
     return EqualityComparer<TKey>.Default.Equals(_CompiledFunc(obj1), _CompiledFunc(obj2)); 
    } 

    public int GetHashCode(T obj) 
    { 
     return EqualityComparer<TKey>.Default.GetHashCode(_CompiledFunc(obj)); 
    } 
} 

usare in questo modo

ArrayList.Sort(new Comparer2<Product, string>(p => p.Name)); 
+2

Perché non accettare semplicemente 'Func '? – AlexFoxGill

+0

[Semplicemente usando 'Func '] (http://stackoverflow.com/a/1239337/2122718), ma grazie per aver mostrato come usare Expresstion – marbel82

18

io non sono molto sicuro di cosa utile che realmente è, come penso per la maggior parte dei casi nella libreria di base che prevedono un IComparer c'è un sovraccarico che si aspetta un confronto ... ma solo per la cronaca:

in .Net 4.5 hanno aggiunto un metodo per ottenere un IComparer da un confronto: Comparer.Create

così puoi passare il tuo lambda ad esso e ottenere un IComparer.

+0

Sarebbe relativamente semplice aggiungere un metodo di estensione a .NET 4 che realizza la stessa cosa – jessehouwing

+4

Esiste qualcosa di simile disponibile per EqualityComparer? Non ha un metodo Create ma sembra molto strano aggiungere questo utile metodo per Comparer e non EqualityComparer – rdans

+2

@rdans 'EqualityComparer' usa una firma diversa e, inoltre, usa il metodo' GetHashCode'. Quindi non puoi creare facilmente un confronto per questo. – VMAtm

16

Ho cercato su Google anche una soluzione, ma non ne ho trovato nessuna soddisfacente. Così ho creato un EqualityComparerFactory generico:

public static class EqualityComparerFactory<T> 
{ 
    private class MyComparer : IEqualityComparer<T> 
    { 
     private readonly Func<T, int> _getHashCodeFunc; 
     private readonly Func<T, T, bool> _equalsFunc; 

     public MyComparer(Func<T, int> getHashCodeFunc, Func<T, T, bool> equalsFunc) 
     { 
      _getHashCodeFunc = getHashCodeFunc; 
      _equalsFunc = equalsFunc; 
     } 

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

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

    public static IEqualityComparer<T> CreateComparer(Func<T, int> getHashCodeFunc, Func<T, T, bool> equalsFunc) 
    { 
     if (getHashCodeFunc == null) 
      throw new ArgumentNullException("getHashCodeFunc"); 
     if (equalsFunc == null) 
      throw new ArgumentNullException("equalsFunc"); 

     return new MyComparer(getHashCodeFunc, equalsFunc); 
    } 
} 

L'idea è che il metodo CreateComparer richiede due argomenti: un delegato per GetHashCode (T) e un delegato per Equals (T, T)

Esempio :

class Person 
{ 
    public int Id { get; set; } 
    public string LastName { get; set; } 
    public string FirstName { get; set; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var list1 = new List<Person>(new[]{ 
      new Person { Id = 1, FirstName = "Walter", LastName = "White" }, 
      new Person { Id = 2, FirstName = "Jesse", LastName = "Pinkman" }, 
      new Person { Id = 3, FirstName = "Skyler", LastName = "White" }, 
      new Person { Id = 4, FirstName = "Hank", LastName = "Schrader" }, 
     }); 

     var list2 = new List<Person>(new[]{ 
      new Person { Id = 1, FirstName = "Walter", LastName = "White" }, 
      new Person { Id = 4, FirstName = "Hank", LastName = "Schrader" }, 
     }); 


     // We're comparing based on the Id property 
     var comparer = EqualityComparerFactory<Person>.CreateComparer(a => a.Id.GetHashCode(), (a, b) => a.Id==b.Id); 
     var intersection = list1.Intersect(list2, comparer).ToList(); 
    } 
} 
1

Nel caso in cui se avete bisogno di questa funzione per l'uso con lambda e forse due diversi tipi di elementi:

static class IEnumerableExtensions 
{ 
    public static bool SequenceEqual<T1, T2>(this IEnumerable<T1> first, IEnumerable<T2> second, Func<T1, T2, bool> comparer) 
    { 
     if (first == null) 
      throw new NullReferenceException("first"); 

     if (second == null) 
      throw new NullReferenceException("second"); 

     using (IEnumerator<T1> e1 = first.GetEnumerator()) 
     using (IEnumerator<T2> e2 = second.GetEnumerator()) 
     { 
      while (e1.MoveNext()) 
      { 
       if (!(e2.MoveNext() && comparer(e1.Current, e2.Current))) 
        return false; 
      } 

      if (e2.MoveNext()) 
       return false; 
     } 

     return true; 
    } 
}