839 Stimmen

Distinct() mit Lambda?

Richtig, ich habe also eine Aufzählung und möchte verschiedene Werte daraus erhalten.

Verwendung von System.Linq gibt es natürlich eine Erweiterungsmethode namens Distinct . Im einfachen Fall kann es ohne Parameter verwendet werden, wie:

var distinctValues = myStringList.Distinct();

Schön und gut, aber wenn ich eine Aufzählung von Objekten habe, für die ich Gleichheit angeben muss, ist die einzige verfügbare Überladung:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

Das Argument des Gleichheitsvergleichers muss eine Instanz von IEqualityComparer<T> . Das kann ich natürlich auch tun, aber es ist etwas langatmig und, nun ja, unübersichtlich.

Was ich erwartet hätte, ist eine Überladung, die ein Lambda, sagen wir ein Func<T, T, bool> :

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

Weiß jemand, ob es eine solche Erweiterung oder eine gleichwertige Umgehung gibt? Oder übersehe ich etwas?

Gibt es alternativ dazu eine Möglichkeit, eine IEqualityComparer inline (mich blamieren)?

Update

Ich habe eine Antwort von Anders Hejlsberg auf eine Beitrag in einem MSDN-Forum zu diesem Thema. Er sagt:

Das Problem, auf das Sie stoßen werden, ist, dass beim Vergleich zweier Objekte gleich sind, müssen sie denselben GetHashCode-Rückgabewert haben (oder die Hash-Tabelle, die intern von Distinct verwendet wird, nicht korrekt funktionieren). Wir verwenden IEqualityComparer, weil es kompatible Implementierungen von Equals und GetHashCode in einer einzigen Schnittstelle zusammenfasst.

Ich denke, das macht Sinn.

2 Stimmen

Siehe stackoverflow.com/questions/1183403/ für eine Lösung mit GroupBy

0 Stimmen

Nein, das ergibt keinen Sinn - wie können zwei Objekte, die identische Werte enthalten, zwei verschiedene Hash-Codes zurückgeben?

1 Stimmen

Es könnte helfen - Lösung para .Distinct(new KeyEqualityComparer<Customer,string>(c1 => c1.CustomerId)) und erklären, warum GetHashCode() wichtig ist, damit es richtig funktioniert.

21voto

JaredPar Punkte 699699

Nein, es gibt keine solche Überladung der Erweiterungsmethode für diese Aufgabe. Ich habe dies in der Vergangenheit selbst als frustrierend empfunden und schreibe daher normalerweise eine Hilfsklasse, um dieses Problem zu lösen. Das Ziel ist die Konvertierung einer Func<T,T,bool> a IEqualityComparer<T,T> .

Ejemplo

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

Damit können Sie Folgendes schreiben

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

9 Stimmen

Die Implementierung des Hash-Codes ist allerdings nicht ganz einfach. Es ist einfacher, eine IEqualityComparer<T> aus einer Projektion: stackoverflow.com/questions/188120/

7 Stimmen

(Nur um meine Bemerkung über den Hash-Code zu erklären - es ist sehr einfach, mit diesem Code zu enden mit Equals(x, y) == true, aber GetHashCode(x) != GetHashCode(y). Das macht im Grunde alles kaputt, was wie eine Hashtabelle aussieht).

0 Stimmen

@Jon, ja ich stimme die ursprüngliche Implementierung von GetHashcode ist weniger als optimal (war faul). Ich habe es geändert, um im Wesentlichen verwenden jetzt EqualityComparer<T>.Default.GetHashcode(), die etwas mehr Standard ist. Ehrlich gesagt, ist die einzige garantiert funktionierende GetHashcode-Implementierung in diesem Szenario, einfach einen konstanten Wert zurückzugeben. Das tötet die Hashtable-Suche, ist aber garantiert funktional korrekt.

14voto

David Kirkland Punkte 2411

Hier ist eine einfache Erweiterung Methode, die tut, was ich brauche ...

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

Es ist eine Schande, dass sie eine solche Methode nicht in den Rahmen eingebaut haben, aber was soll's.

1 Stimmen

Aber, ich musste mich ändern x.Key a x.First() und ändern Sie den Rückgabewert in IEnumerable<T>

0 Stimmen

@toddmo Danke für das Feedback :-) Ja, klingt logisch... Ich werde die Antwort nach weiteren Nachforschungen aktualisieren.

13voto

Gordon Freeman Punkte 147

Damit erreichen Sie, was Sie wollen, aber ich weiß nicht, wie es mit der Leistung aussieht:

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

Wenigstens ist es nicht langatmig.

4voto

Dmitry Ledentsov Punkte 3540

Alle Lösungen, die ich hier gesehen habe, beruhen auf der Auswahl eines bereits vergleichbaren Feldes. Wenn man jedoch auf eine andere Art und Weise vergleichen muss, diese Lösung hier scheint im Allgemeinen zu funktionieren, z. B. für:

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()

0 Stimmen

Was ist LambdaComparer, woher importieren Sie das?

0 Stimmen

@PatrickGraham hat die Antwort verlinkt: brendan.enrick.com/post/

4voto

Kleinux Punkte 1451

Etwas, das ich verwendet habe und das bei mir gut funktioniert hat.

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

0 Stimmen

@Mukus Ich bin mir nicht sicher, warum Sie hier nach dem Klassennamen fragen. Ich musste der Klasse einen Namen geben, um IEqualityComparer zu implementieren, also habe ich einfach das My vorangestellt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X