write - static class extension method c#




Distinct() mit Lambda? (12)

Alle Lösungen, die ich hier gesehen habe, sind darauf angewiesen, ein bereits vergleichbares Feld auszuwählen. Wenn man jedoch auf eine andere Weise vergleichen möchte, scheint diese Lösung hier allgemein zu funktionieren, für etwas wie:

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

Richtig, also habe ich ein Aufzählungszeichen und möchte bestimmte Werte daraus ableiten.

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

var distinctValues = myStringList.Distinct();

Gut 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 Gleichheitsvergleichsargument muss eine Instanz von IEqualityComparer<T> . Ich kann das natürlich, aber es ist etwas ausführlich und, naja, Clodgy.

Was ich erwartet hätte, ist eine Überlastung, die ein Lambda, sagen wir ein Func <T, T, bool> nehmen würde:

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

Wer weiß, ob es eine solche Erweiterung oder eine gleichwertige Problemumgehung gibt? Oder fehlt mir etwas?

Oder gibt es eine Möglichkeit, einen IEqualityComparer inline (embaras me) anzugeben?

Aktualisieren

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

Das Problem, mit dem Sie konfrontiert werden, ist, dass, wenn zwei Objekte gleich sind, sie den gleichen GetHashCode-Rückgabewert haben müssen (sonst wird die von Distinct intern verwendete Hash-Tabelle nicht korrekt funktionieren). Wir verwenden IEqualityComparer, weil es kompatible Implementierungen von Equals und GetHashCode in eine einzige Schnittstelle packt.

Ich denke, das macht Sinn.


Das Microsoft System.Interactive-Paket verfügt über eine Version von Distinct, die ein Schlüsselselektor Lambda übernimmt. Das ist praktisch dasselbe wie die Lösung von Jon Skeet, aber es kann hilfreich sein, um den Rest der Bibliothek zu kennen und zu überprüfen.


Eine knifflige Methode hierfür ist die Verwendung der Aggregate() -Erweiterung, die ein Wörterbuch als Akkumulator mit den key-property- Werten als Schlüssel verwendet:

var customers = new List<Customer>();

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

Und eine GroupBy-ähnliche Lösung verwendet ToLookup() :

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

Es sieht für mich so aus, als wollten Sie DistinctBy von MoreLINQ . Sie können dann schreiben:

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

Hier ist eine abgespeckte Version von DistinctBy (keine Nichtigkeitsüberprüfung und keine Möglichkeit, einen eigenen Schlüsselvergleich anzugeben):

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

Hier ist eine einfache Erweiterungsmethode, die das 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 nicht eine bestimmte Methode wie diese in den Rahmen gebrannt haben, aber hey ho.


Hier ist, wie Sie es tun können:

public static class Extensions
{
    public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
                                                    Func<T, V> f, 
                                                    Func<IGrouping<V,T>,T> h=null)
    {
        if (h==null) h=(x => x.First());
        return query.GroupBy(f).Select(h);
    }
}

Mit dieser Methode können Sie einen Parameter wie .MyDistinct(d => d.Name) , aber Sie können auch eine .MyDistinct(d => d.Name) Bedingung als zweiten Parameter angeben:

var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name,
        x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2"))
        );

Hinweis: Damit können Sie auch andere Funktionen wie zum Beispiel .LastOrDefault(...) angeben.

Wenn Sie nur die Bedingung aufdecken möchten, können Sie es noch einfacher machen, indem Sie es wie folgt implementieren:

public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
                                                Func<T, V> f,
                                                Func<T,bool> h=null
                                                )
{
    if (h == null) h = (y => true);
    return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}

In diesem Fall würde die Abfrage wie folgt aussehen:

var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
                    y => y.Name.Contains("1") || y.Name.Contains("2")
                    );

NB Hier ist der Ausdruck einfacher, aber beachten Sie. .MyDistinct2 verwendet .FirstOrDefault(...) implizit.

Hinweis: In den obigen Beispielen wird die folgende Demoklasse verwendet

class MyObject
{
    public string Name;
    public string Code;
}

private MyObject[] _myObject = {
    new MyObject() { Name = "Test1", Code = "T"},
    new MyObject() { Name = "Test2", Code = "Q"},
    new MyObject() { Name = "Test2", Code = "T"},
    new MyObject() { Name = "Test5", Code = "Q"}
};

Kurzschrift-Lösung

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

Nein, dafür gibt es keine Überladung der Erweiterungsmethode. Ich habe das selbst in der Vergangenheit als frustrierend empfunden und schreibe normalerweise eine Hilfsklasse, um mit diesem Problem umzugehen. Ziel ist es, einen Func<T,T,bool> in IEqualityComparer<T,T> .

Beispiel

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

So können Sie Folgendes schreiben

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

Sie können InlineComparer verwenden

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

Verwendungsbeispiel :

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

Quelle: https://.com/a/5969691/206730
IEqualityComparer für Union verwenden
Kann ich meinen expliziten Typvergleicher inline angeben?


Wenn Distinct() keine eindeutigen Ergebnisse liefert, versuchen Sie Folgendes:

var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID); 

ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);

Um die Dinge einpacken . Ich denke, die meisten Leute, die wie ich hierher gekommen sind, wollen die einfachste Lösung ohne Bibliotheken und mit bestmöglicher Leistung .

(Die akzeptierte Gruppe von Methode für mich denke ich ist ein Overkill in Bezug auf die Leistung.)

Hier ist eine einfache Erweiterungsmethode, die die IEqualityComparer- Schnittstelle verwendet, die auch für Nullwerte funktioniert.

Verwendung:

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

Erweiterungsmethode

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

IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());




extension-methods