2008-10-09 6 views
43

So bietet uns .NET 3.0/3.5 viele neue Möglichkeiten, Daten abzufragen, zu sortieren und zu bearbeiten, dank der vielen Funktionen, die LINQ bietet. Manchmal muss ich benutzerdefinierte Typen vergleichen, die keinen integrierten Vergleichsoperator haben. In vielen Fällen ist der Vergleich sehr einfach - etwas wie foo1.key? = Foo2.key. Anstatt einen neuen IEqualityComparer für den Typ zu erstellen, kann ich einfach den Vergleich inline mit anonymen Delegaten/Lambda-Funktionen angeben? Etwas wie:Kann ich meinen expliziten Typvergleicher inline angeben?

var f1 = ..., 
    f2 = ...; 
var f3 = f1.Except(
      f2, new IEqualityComparer(
      (Foo a, Foo b) => a.key.CompareTo(b.key) 
      )); 

Ich bin ziemlich sicher, dass das oben genannte nicht wirklich funktioniert. Ich will einfach nicht etwas so "schwer" machen wie eine ganze Klasse, nur um dem Programm zu sagen, wie man Äpfel mit Äpfeln vergleicht.

Antwort

55

Meine MiscUtil Bibliothek enthält einen ProjectionComparer, um einen IComparer < T> von einem Projektionsdelegaten zu erstellen. Es wäre die Arbeit von 10 Minuten, einen ProjectionEqualityComparer dazu zu bringen, dasselbe zu tun.

EDIT: Hier ist der Code für ProjectionEqualityComparer:

using System; 
using System.Collections.Generic; 

/// <summary> 
/// Non-generic class to produce instances of the generic class, 
/// optionally using type inference. 
/// </summary> 
public static class ProjectionEqualityComparer 
{ 
    /// <summary> 
    /// Creates an instance of ProjectionEqualityComparer using the specified projection. 
    /// </summary> 
    /// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam> 
    /// <typeparam name="TKey">Type parameter for the keys to be compared, 
    /// after being projected from the elements</typeparam> 
    /// <param name="projection">Projection to use when determining the key of an element</param> 
    /// <returns>A comparer which will compare elements by projecting 
    /// each element to its key, and comparing keys</returns> 
    public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>(Func<TSource, TKey> projection) 
    { 
     return new ProjectionEqualityComparer<TSource, TKey>(projection); 
    } 

    /// <summary> 
    /// Creates an instance of ProjectionEqualityComparer using the specified projection. 
    /// The ignored parameter is solely present to aid type inference. 
    /// </summary> 
    /// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam> 
    /// <typeparam name="TKey">Type parameter for the keys to be compared, 
    /// after being projected from the elements</typeparam> 
    /// <param name="ignored">Value is ignored - type may be used by type inference</param> 
    /// <param name="projection">Projection to use when determining the key of an element</param> 
    /// <returns>A comparer which will compare elements by projecting 
    /// each element to its key, and comparing keys</returns> 
    public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey> 
     (TSource ignored, 
     Func<TSource, TKey> projection) 
    { 
     return new ProjectionEqualityComparer<TSource, TKey>(projection); 
    } 

} 

/// <summary> 
/// Class generic in the source only to produce instances of the 
/// doubly generic class, optionally using type inference. 
/// </summary> 
public static class ProjectionEqualityComparer<TSource> 
{ 
    /// <summary> 
    /// Creates an instance of ProjectionEqualityComparer using the specified projection. 
    /// </summary> 
    /// <typeparam name="TKey">Type parameter for the keys to be compared, 
    /// after being projected from the elements</typeparam> 
    /// <param name="projection">Projection to use when determining the key of an element</param> 
    /// <returns>A comparer which will compare elements by projecting each element to its key, 
    /// and comparing keys</returns>   
    public static ProjectionEqualityComparer<TSource, TKey> Create<TKey>(Func<TSource, TKey> projection) 
    { 
     return new ProjectionEqualityComparer<TSource, TKey>(projection); 
    } 
} 

/// <summary> 
/// Comparer which projects each element of the comparison to a key, and then compares 
/// those keys using the specified (or default) comparer for the key type. 
/// </summary> 
/// <typeparam name="TSource">Type of elements which this comparer 
/// will be asked to compare</typeparam> 
/// <typeparam name="TKey">Type of the key projected 
/// from the element</typeparam> 
public class ProjectionEqualityComparer<TSource, TKey> : IEqualityComparer<TSource> 
{ 
    readonly Func<TSource, TKey> projection; 
    readonly IEqualityComparer<TKey> comparer; 

    /// <summary> 
    /// Creates a new instance using the specified projection, which must not be null. 
    /// The default comparer for the projected type is used. 
    /// </summary> 
    /// <param name="projection">Projection to use during comparisons</param> 
    public ProjectionEqualityComparer(Func<TSource, TKey> projection) 
     : this(projection, null) 
    { 
    } 

    /// <summary> 
    /// Creates a new instance using the specified projection, which must not be null. 
    /// </summary> 
    /// <param name="projection">Projection to use during comparisons</param> 
    /// <param name="comparer">The comparer to use on the keys. May be null, in 
    /// which case the default comparer will be used.</param> 
    public ProjectionEqualityComparer(Func<TSource, TKey> projection, IEqualityComparer<TKey> comparer) 
    { 
     if (projection == null) 
     { 
      throw new ArgumentNullException("projection"); 
     } 
     this.comparer = comparer ?? EqualityComparer<TKey>.Default; 
     this.projection = projection; 
    } 

    /// <summary> 
    /// Compares the two specified values for equality by applying the projection 
    /// to each value and then using the equality comparer on the resulting keys. Null 
    /// references are never passed to the projection. 
    /// </summary> 
    public bool Equals(TSource x, TSource y) 
    { 
     if (x == null && y == null) 
     { 
      return true; 
     } 
     if (x == null || y == null) 
     { 
      return false; 
     } 
     return comparer.Equals(projection(x), projection(y)); 
    } 

    /// <summary> 
    /// Produces a hash code for the given value by projecting it and 
    /// then asking the equality comparer to find the hash code of 
    /// the resulting key. 
    /// </summary> 
    public int GetHashCode(TSource obj) 
    { 
     if (obj == null) 
     { 
      throw new ArgumentNullException("obj"); 
     } 
     return comparer.GetHashCode(projection(obj)); 
    } 
} 

Und hier ist eine Probe Verwendung:

var f3 = f1.Except(f2, ProjectionEqualityComparer<Foo>.Create(a => a.key)); 
+0

Während ich den Link zu schätzen wissen, ich denke, es könnte ein bisschen einfacher für Menschen sein, die Antwort zu lesen, um herauszufinden, wie dies zu tun, wenn Sie einen Ausschnitt hier auf SO einfügen könnten, anstatt zu müssen Ihre Website, herunterladen gehen und Entpacken Sie die Quelle und blättern Sie dann durch die Suche nach einer bestimmten Klasse. Bitte? – Coderer

+0

Ich werde beides tun - aber es wird wahrscheinlich ein gutes Stück Code seines wegen Bequemlichkeit Methoden usw. –

+6

Nun ist die einzige Frage ist, warum nicht diese Art der Sache in die Sprache gebaut? – Coderer

8

Ich finde die Bereitstellung zusätzlicher Helfer auf IEnumerable ist ein sauberer Weg, dies zu tun.

See: this question

So könnten Sie haben:

var f3 = f1.Except(
      f2, 
      (a, b) => a.key.CompareTo(b.key) 
      ); 

Wenn Sie die Erweiterungsmethoden definieren richtig

+3

Ich wünsche, wir können dies tun, ohne die Erweiterungsmethoden – Jaider

19

hier ist eine einfache Hilfsklasse, die tun sollten, was Sie wollen

public class EqualityComparer<T> : IEqualityComparer<T> 
{ 
    public EqualityComparer(Func<T, T, bool> cmp) 
    { 
     this.cmp = cmp; 
    } 
    public bool Equals(T x, T y) 
    { 
     return cmp(x, y); 
    } 

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

    public Func<T, T, bool> cmp { get; set; } 
} 

können Sie es wie folgt verwenden:

processed.Union(suburbs, new EqualityComparer<Suburb>((s1, s2) 
    => s1.SuburbId == s2.SuburbId)); 
+9

Dies funktioniert nicht, weil Union und Distinct zuerst den Hash-Code überprüfen, die unabhängig von verschieden sein können, was die Delegierten sagen. Das Ändern von GetHashCode, um immer 0 zurückzugeben, behebt das Problem. – makhdumi

6

Dieses Projekt macht etwas ähnliches: AnonymousComparer - lambda compare selector for Linq, es hat auch Erweiterungen für LINQ Standard Query Operatoren.

+0

Das verdient wirklich mehr Upvotes. Eine der praktischsten Bibliotheken, die ich bei der Verwendung von Lambda/Linq erworben habe – Nathan

2

Warum nicht so etwas wie:

public class Comparer<T> : IEqualityComparer<T> 
    { 
     private readonly Func<T, T, bool> _equalityComparer; 

     public Comparer(Func<T, T, bool> equalityComparer) 
     { 
      _equalityComparer = equalityComparer; 
     } 

     public bool Equals(T first, T second) 
     { 
      return _equalityComparer(first, second); 
     } 

     public int GetHashCode(T value) 
     { 
      return value.GetHashCode(); 
     } 
    } 

und dann könnte man zum Beispiel etwas tun, wie (zum Beispiel im Fall von Intersect in IEnumerable<T>):

list.Intersect(otherList, new Comparer<T>((x, y) => x.Property == y.Property)); 

Die Comparer Klasse kann in gesetzt werden ein Hilfsprojekt und wird überall dort eingesetzt, wo es benötigt wird.

Ich sehe jetzt nur die Antwort von Sam Saffron (die dieser sehr ähnlich ist).

0

Für kleine Sets können Sie tun:

f3 = f1.Where(x1 => f2.All(x2 => x2.key != x1.key)); 

Für große Mengen, werden Sie sich wie etwas effizienter bei der Suche wollen:

var tmp = new HashSet<string>(f2.Select(f => f.key)); 
f3 = f1.Where(f => tmp.Add(f.key)); 

Aber hier, die Type von Schlüssel muss implementieren IEqualityComparer (oben nahm ich an, es war ein string). Also, das beantwortet nicht wirklich deine Frage über die Verwendung eines Lambda in dieser Situation, aber es verwendet weniger Code als einige der Antworten, die das tun.

Sie könnten auf dem Optimierer verlassen und die zweite Lösung verkürzen:

f3 = f1.Where(x1 => (new HashSet<string>(f2.Select(x2 => x2.key))).Add(x1.key)); 

aber, ich habe Tests nicht ausgeführt wissen, ob es mit der gleichen Geschwindigkeit läuft. Und dieser eine Liner könnte zu clever sein, um ihn zu erhalten.