Wickeln Sie einen Delegaten in einen IEqualityComparer ein

127

Mehrere Linq.Enumerable-Funktionen benötigen eine IEqualityComparer<T>. Gibt es eine praktische Wrapper-Klasse, die a delegate(T,T)=>boolan die Implementierung anpasst IEqualityComparer<T>? Es ist einfach genug, einen zu schreiben (wenn Sie Probleme mit der Definition eines korrekten Hashcodes ignorieren), aber ich würde gerne wissen, ob es eine sofort einsatzbereite Lösung gibt.

Insbesondere möchte ich Set-Operationen für Dictionarys ausführen und nur die Schlüssel zum Definieren der Mitgliedschaft verwenden (wobei die Werte nach verschiedenen Regeln beibehalten werden).

Marcelo Cantos
quelle

Antworten:

44

Normalerweise würde ich dieses Problem lösen, indem ich @Sam zu der Antwort kommentiere (ich habe den ursprünglichen Beitrag bearbeitet, um ihn ein wenig zu bereinigen, ohne das Verhalten zu ändern.)

Das Folgende ist mein Riff von @ Sams Antwort mit einer [IMNSHO] kritischen Korrektur der Standard-Hashing-Richtlinie: -

class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => 0 ) // NB Cannot assume anything about how e.g., t.GetHashCode() interacts with the comparer's behavior
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

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

    public int GetHashCode( T obj )
    {
        return _hash( obj );
    }
}
Ruben Bartelink
quelle
5
Für mich ist dies die richtige Antwort. Alles IEqualityComparer<T>, was weggelassen wird GetHashCode, ist einfach kaputt.
Dan Tao
1
@ Joshua Frank: Es ist nicht gültig, Hash-Gleichheit zu verwenden, um Gleichheit zu implizieren - nur das Gegenteil ist wahr. Kurz gesagt, @Dan Tao ist völlig korrekt in dem, was er sagt, und diese Antwort ist einfach die Anwendung dieser Tatsache auf eine zuvor unvollständige Antwort
Ruben Bartelink
2
@Ruben Bartelink: Danke für die Klarstellung. Aber ich verstehe Ihre Hashing-Richtlinie von t => 0 immer noch nicht. Wenn alle Objekte immer auf dasselbe (Null) hashen, ist das dann nicht noch mehr kaputt als die Verwendung von obj.GetHashCode gemäß @ Dan Taos Punkt? Warum nicht immer den Anrufer zwingen, eine gute Hash-Funktion bereitzustellen?
Joshua Frank
1
Es ist daher nicht vernünftig anzunehmen, dass ein beliebiger Algorithmus in einer gelieferten Funktion möglicherweise nicht true zurückgeben kann, obwohl die Hash-Codes unterschiedlich sind. Ihr Punkt, dass es einfach kein Hashing ist, immer Null zurückzugeben, ist wahr. Aus diesem Grund gibt es eine Überlastung, die den Hashing-Func benötigt, wenn der Profiler uns mitteilt, dass die Suche nicht effizient genug ist. Der einzige Punkt bei all dem ist, dass wenn Sie einen Standard-Hashing-Algorithmus haben, dieser 100% der Zeit funktionieren sollte und kein gefährliches oberflächlich korrektes Verhalten aufweist. Und dann können wir an der Leistung arbeiten!
Ruben Bartelink
4
Mit anderen Worten, da Sie einen benutzerdefinierten Vergleicher verwenden, hat dieser nichts mit dem Standard- Hash-Code des Objekts zu tun, der sich auf den Standard- Vergleicher bezieht. Daher können Sie ihn nicht verwenden.
Peet Brits
170

Über die Bedeutung von GetHashCode

Andere haben bereits kommentiert, dass jede benutzerdefinierte IEqualityComparer<T>Implementierung wirklich eine GetHashCodeMethode enthalten sollte . aber niemand hat sich die Mühe gemacht, im Detail zu erklären, warum .

Hier ist der Grund. In Ihrer Frage werden speziell die LINQ-Erweiterungsmethoden erwähnt. Fast alle von ihnen sind auf Hash-Codes angewiesen, um ordnungsgemäß zu funktionieren, da sie aus Effizienzgründen intern Hash-Tabellen verwenden.

Nehmen wir Distinctzum Beispiel. Berücksichtigen Sie die Auswirkungen dieser Erweiterungsmethode, wenn nur eine EqualsMethode verwendet wurde. Wie stellen Sie fest, ob ein Artikel bereits in einer Sequenz gescannt wurde, wenn Sie nur haben Equals? Sie zählen die gesamte Sammlung von Werten auf, die Sie bereits angesehen haben, und suchen nach Übereinstimmungen. Dies würde dazu führen, dass anstelle eines O (N) -Algorithmus ein O (N 2 ) -Algorithmus im Distinctungünstigsten Fall verwendet wird !

Zum Glück ist dies nicht der Fall. Distinctnicht nur verwenden Equals; es verwendet GetHashCodeauch. In der Tat funktioniert es absolut nicht richtig ohne eine IEqualityComparer<T>, die eine ordnungsgemäße liefertGetHashCode . Unten sehen Sie ein Beispiel, das dies veranschaulicht.

Angenommen, ich habe den folgenden Typ:

class Value
{
    public string Name { get; private set; }
    public int Number { get; private set; }

    public Value(string name, int number)
    {
        Name = name;
        Number = number;
    }

    public override string ToString()
    {
        return string.Format("{0}: {1}", Name, Number);
    }
}

Sagen Sie jetzt, ich habe eine List<Value>und ich möchte alle Elemente mit einem eindeutigen Namen finden. Dies ist ein perfekter Anwendungsfall für die DistinctVerwendung eines benutzerdefinierten Gleichheitsvergleichs. Verwenden wir also die Comparer<T>Klasse aus Akus Antwort :

var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);

Wenn wir nun eine Reihe von ValueElementen mit derselben NameEigenschaft haben, sollten sie alle zu einem Wert zusammenfallen, der von zurückgegeben wird Distinct, oder? Mal schauen...

var values = new List<Value>();

var random = new Random();
for (int i = 0; i < 10; ++i)
{
    values.Add("x", random.Next());
}

var distinct = values.Distinct(comparer);

foreach (Value x in distinct)
{
    Console.WriteLine(x);
}

Ausgabe:

x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

Hmm, das hat nicht funktioniert, oder?

Was ist mit GroupBy? Versuchen wir das mal:

var grouped = values.GroupBy(x => x, comparer);

foreach (IGrouping<Value> g in grouped)
{
    Console.WriteLine("[KEY: '{0}']", g);
    foreach (Value x in g)
    {
        Console.WriteLine(x);
    }
}

Ausgabe:

[KEY = 'x: 1346013431']
x: 1346013431
[KEY = 'x: 1388845717']
x: 1388845717
[KEY = 'x: 1576754134']
x: 1576754134
[KEY = 'x: 1104067189']
x: 1104067189
[KEY = 'x: 1144789201']
x: 1144789201
[KEY = 'x: 1862076501']
x: 1862076501
[KEY = 'x: 1573781440']
x: 1573781440
[KEY = 'x: 646797592']
x: 646797592
[KEY = 'x: 655632802']
x: 655632802
[KEY = 'x: 1206819377']
x: 1206819377

Wieder: hat nicht funktioniert.

Wenn Sie darüber nachdenken, wäre es sinnvoll Distinct, ein HashSet<T>(oder ein gleichwertiges) intern GroupByzu verwenden und so etwas wie ein Dictionary<TKey, List<T>>internes zu verwenden. Könnte dies erklären, warum diese Methoden nicht funktionieren? Lass uns das versuchen:

var uniqueValues = new HashSet<Value>(values, comparer);

foreach (Value x in uniqueValues)
{
    Console.WriteLine(x);
}

Ausgabe:

x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

Ja ... fängt es an Sinn zu machen?

Aus diesen Beispielen geht hoffentlich klar hervor, warum es so wichtig ist, GetHashCodein jede IEqualityComparer<T>Implementierung ein geeignetes Element aufzunehmen.


Ursprüngliche Antwort

Die Antwort von orip erweitern :

Hier können einige Verbesserungen vorgenommen werden.

  1. Zuerst würde ich ein Func<T, TKey>statt nehmen Func<T, object>; Dies verhindert das Boxen von Werttypschlüsseln im tatsächlichen keyExtractorselbst.
  2. Zweitens würde ich tatsächlich eine where TKey : IEquatable<TKey>Einschränkung hinzufügen . Dies verhindert das Boxen im EqualsAufruf ( object.Equalsnimmt einen objectParameter an; Sie benötigen eine IEquatable<TKey>Implementierung, um einen TKeyParameter zu nehmen , ohne ihn zu boxen). Dies kann eindeutig eine zu strenge Einschränkung darstellen, sodass Sie eine Basisklasse ohne die Einschränkung und eine abgeleitete Klasse damit erstellen können.

So könnte der resultierende Code aussehen:

public class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    protected readonly Func<T, TKey> keyExtractor;

    public KeyEqualityComparer(Func<T, TKey> keyExtractor)
    {
        this.keyExtractor = keyExtractor;
    }

    public virtual bool Equals(T x, T y)
    {
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }

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

public class StrictKeyEqualityComparer<T, TKey> : KeyEqualityComparer<T, TKey>
    where TKey : IEquatable<TKey>
{
    public StrictKeyEqualityComparer(Func<T, TKey> keyExtractor)
        : base(keyExtractor)
    { }

    public override bool Equals(T x, T y)
    {
        // This will use the overload that accepts a TKey parameter
        // instead of an object parameter.
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }
}
Dan Tao
quelle
1
Ihre StrictKeyEqualityComparer.EqualsMethode scheint dieselbe zu sein wie KeyEqualityComparer.Equals. Funktioniert die TKey : IEquatable<TKey>Einschränkung TKey.Equalsanders?
Justin Morgan
2
@JustinMorgan: Ja - im ersten Fall verwendet TKeyder Compiler , da es sich um einen beliebigen Typ handeln kann, die virtuelle Methode, bei Object.Equalsder Werttypparameter in Boxen gesetzt werden müssen, z int. Im letzteren Fall wird jedoch, da TKeydie Implementierung eingeschränkt ist IEquatable<TKey>, die TKey.EqualsMethode verwendet, für die kein Boxen erforderlich ist.
Dan Tao
2
Sehr interessant, danke für die Info. Ich hatte keine Ahnung, dass GetHashCode diese LINQ-Implikationen hatte, bis ich diese Antworten sah. Gut zu wissen für die zukünftige Verwendung.
Justin Morgan
1
@JohannesH: Wahrscheinlich! Hätte auch die Notwendigkeit beseitigt StringKeyEqualityComparer<T, TKey>.
Dan Tao
1
+1 @DanTao: Verspäteter Dank für eine großartige Erklärung, warum man Hash-Codes bei der Definition von Gleichheit in .Net niemals ignorieren sollte.
Marcelo Cantos
118

Wenn Sie die Gleichheitsprüfung anpassen möchten, möchten Sie in 99% der Fälle die zu vergleichenden Schlüssel definieren, nicht den Vergleich selbst.

Dies könnte eine elegante Lösung sein (Konzept aus Pythons Listensortiermethode ).

Verwendung:

var foo = new List<string> { "abc", "de", "DE" };

// case-insensitive distinct
var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );

Die KeyEqualityComparerKlasse:

public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, object> keyExtractor;

    public KeyEqualityComparer(Func<T,object> keyExtractor)
    {
        this.keyExtractor = keyExtractor;
    }

    public bool Equals(T x, T y)
    {
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }

    public int GetHashCode(T obj)
    {
        return this.keyExtractor(obj).GetHashCode();
    }
}
orip
quelle
3
Das ist viel besser als Akus Antwort.
SLaks
Auf jeden Fall der richtige Ansatz. Meiner Meinung nach können einige Verbesserungen vorgenommen werden, die ich in meiner eigenen Antwort erwähnt habe.
Dan Tao
1
Dies ist ein sehr eleganter Code, der jedoch die Frage nicht beantwortet, weshalb ich stattdessen die Antwort von @ aku akzeptiert habe. Ich wollte einen Wrapper für Func <T, T, bool> und muss keinen Schlüssel extrahieren, da der Schlüssel bereits in meinem Wörterbuch getrennt ist.
Marcelo Cantos
6
@Marcelo: Das ist in Ordnung, das kannst du machen; Beachten Sie jedoch, dass Sie, wenn Sie den Ansatz von @ aku verfolgen möchten, wirklich a hinzufügen solltenFunc<T, int> , um den Hash-Code für einen TWert anzugeben (wie z. B. in Rubens Antwort vorgeschlagen ). Andernfalls ist die IEqualityComparer<T>Implementierung, die Ihnen verbleibt, ziemlich fehlerhaft, insbesondere im Hinblick auf ihre Nützlichkeit in LINQ-Erweiterungsmethoden. In meiner Antwort finden Sie eine Diskussion darüber, warum dies so ist.
Dan Tao
Das ist schön, aber wenn der ausgewählte Schlüssel ein Werttyp wäre, würde unnötiges Boxen entstehen. Vielleicht wäre es besser, einen TKey zum Definieren des Schlüssels zu haben.
Graham Ambrose
48

Ich fürchte, es gibt keine solche Verpackung, die sofort einsatzbereit ist. Es ist jedoch nicht schwer, eine zu erstellen:

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

    public Comparer(Func<T, T, bool> comparer)
    {
        if (comparer == null)
            throw new ArgumentNullException("comparer");

        _comparer = comparer;
    }

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

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

...

Func<int, int, bool> f = (x, y) => x == y;
var comparer = new Comparer<int>(f);
Console.WriteLine(comparer.Equals(1, 1));
Console.WriteLine(comparer.Equals(1, 2));
aku
quelle
1
Seien Sie jedoch vorsichtig mit dieser Implementierung von GetHashCode. Wenn Sie es tatsächlich in einer Art Hash-Tabelle verwenden möchten, möchten Sie etwas Robusteres.
Thecoop
46
Dieser Code hat ein ernstes Problem! Es ist einfach, eine Klasse zu erstellen, die zwei Objekte enthält, die in Bezug auf diesen Vergleich vergleichbar sind, aber unterschiedliche Hash-Codes haben.
Empi
10
Um dies zu beheben, benötigt die Klasse ein anderes Mitglied private readonly Func<T, int> _hashCodeResolver, das ebenfalls im Konstruktor übergeben und in der GetHashCode(...)Methode verwendet werden muss.
Herzmeister
6
Ich bin neugierig: Warum benutzt du obj.ToString().ToLower().GetHashCode()statt obj.GetHashCode()?
Justin Morgan
3
Die Stellen im Framework, an denen IEqualityComparer<T>ausnahmslos Hashing hinter den Kulissen verwendet wird (z. B. GroupBy, Distinct, Except, Join usw. von LINQ), und der MS-Vertrag in Bezug auf Hashing sind in dieser Implementierung fehlerhaft. Hier ist der Dokumentationsauszug von MS: "Implementierungen sind erforderlich, um sicherzustellen, dass der von der GetHashCode-Methode für x zurückgegebene Wert dem für y zurückgegebenen Wert entsprechen muss, wenn die Equals-Methode für zwei Objekte x und y true zurückgibt." Siehe: msdn.microsoft.com/en-us/library/ms132155
devgeezer
22

Wie Dan Taos Antwort, jedoch mit einigen Verbesserungen:

  1. Verlässt sich auf EqualityComparer<>.Defaultden eigentlichen Vergleich, um das Boxen für structimplementierte Werttypen zu vermeiden IEquatable<>.

  2. Seit seiner EqualityComparer<>.DefaultVerwendung explodiert es nicht weiter null.Equals(something).

  3. Bereitgestellter statischer Wrapper, IEqualityComparer<>der eine statische Methode zum Erstellen der Instanz des Vergleichers enthält - erleichtert das Aufrufen. Vergleichen Sie

    Equality<Person>.CreateComparer(p => p.ID);

    mit

    new EqualityComparer<Person, int>(p => p.ID);
  4. Es wurde eine Überladung hinzugefügt, die IEqualityComparer<>für den Schlüssel angegeben werden soll.

Die Klasse:

public static class Equality<T>
{
    public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector)
    {
        return CreateComparer(keySelector, null);
    }

    public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector, 
                                                         IEqualityComparer<V> comparer)
    {
        return new KeyEqualityComparer<V>(keySelector, comparer);
    }

    class KeyEqualityComparer<V> : IEqualityComparer<T>
    {
        readonly Func<T, V> keySelector;
        readonly IEqualityComparer<V> comparer;

        public KeyEqualityComparer(Func<T, V> keySelector, 
                                   IEqualityComparer<V> comparer)
        {
            if (keySelector == null)
                throw new ArgumentNullException("keySelector");

            this.keySelector = keySelector;
            this.comparer = comparer ?? EqualityComparer<V>.Default;
        }

        public bool Equals(T x, T y)
        {
            return comparer.Equals(keySelector(x), keySelector(y));
        }

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

Sie können es so verwenden:

var comparer1 = Equality<Person>.CreateComparer(p => p.ID);
var comparer2 = Equality<Person>.CreateComparer(p => p.Name);
var comparer3 = Equality<Person>.CreateComparer(p => p.Birthday.Year);
var comparer4 = Equality<Person>.CreateComparer(p => p.Name, StringComparer.CurrentCultureIgnoreCase);

Person ist eine einfache Klasse:

class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
}
ldp615
quelle
3
+1 für die Bereitstellung einer Implementierung, mit der Sie einen Vergleicher für den Schlüssel bereitstellen können. Dies bietet nicht nur mehr Flexibilität, sondern vermeidet auch Boxwerttypen sowohl für die Vergleiche als auch für das Hashing.
Devgeezer
2
Dies ist die ausführlichste Antwort hier. Ich habe auch einen Null-Check hinzugefügt. Komplett.
Nawfal
11
public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => t.GetHashCode())
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

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

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

Mit Erweiterungen: -

public static class SequenceExtensions
{
    public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer )
    {
        return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer ) );
    }

    public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer, Func<T, int> hash )
    {
        return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer, hash ) );
    }
}
Ruben Bartelink
quelle
@Sam (der ab diesem Kommentar nicht mehr existiert): Bereinigter Code ohne Anpassen des Verhaltens (und +1). Riff hinzugefügt bei stackoverflow.com/questions/98033/…
Ruben Bartelink
6

Die Antwort von orip ist großartig.

Hier eine kleine Erweiterungsmethode, um es noch einfacher zu machen:

public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, object>    keyExtractor)
{
    return list.Distinct(new KeyEqualityComparer<T>(keyExtractor));
}
var distinct = foo.Distinct(x => x.ToLower())
Bruno
quelle
2

Ich werde meine eigene Frage beantworten. Um Wörterbücher als Mengen zu behandeln, scheint die einfachste Methode darin zu bestehen, Mengenoperationen auf dict.Keys anzuwenden und dann mit Enumerable.ToDictionary (...) wieder in Wörterbücher zu konvertieren.

Marcelo Cantos
quelle
2

Die Implementierung bei (deutscher Text) Implementierung von IEqualityCompare mit Lambda-Ausdruck kümmert sich um Nullwerte und verwendet Erweiterungsmethoden, um IEqualityComparer zu generieren.

Um einen IEqualityComparer in einer Linq-Union zu erstellen, müssen Sie nur schreiben

persons1.Union(persons2, person => person.LastName)

Der Vergleicher:

public class LambdaEqualityComparer<TSource, TComparable> : IEqualityComparer<TSource>
{
  Func<TSource, TComparable> _keyGetter;

  public LambdaEqualityComparer(Func<TSource, TComparable> keyGetter)
  {
    _keyGetter = keyGetter;
  }

  public bool Equals(TSource x, TSource y)
  {
    if (x == null || y == null) return (x == null && y == null);
    return object.Equals(_keyGetter(x), _keyGetter(y));
  }

  public int GetHashCode(TSource obj)
  {
    if (obj == null) return int.MinValue;
    var k = _keyGetter(obj);
    if (k == null) return int.MaxValue;
    return k.GetHashCode();
  }
}

Sie müssen auch eine Erweiterungsmethode hinzufügen, um die Typinferenz zu unterstützen

public static class LambdaEqualityComparer
{
       // source1.Union(source2, lambda)
        public static IEnumerable<TSource> Union<TSource, TComparable>(
           this IEnumerable<TSource> source1, 
           IEnumerable<TSource> source2, 
            Func<TSource, TComparable> keySelector)
        {
            return source1.Union(source2, 
               new LambdaEqualityComparer<TSource, TComparable>(keySelector));
       }
   }
Gebraten
quelle
1

Nur eine Optimierung: Wir können den sofort einsatzbereiten EqualityComparer für Wertvergleiche verwenden, anstatt ihn zu delegieren.

Dies würde auch die Implementierung übersichtlicher machen, da die tatsächliche Vergleichslogik jetzt in GetHashCode () und Equals () verbleibt, die Sie möglicherweise bereits überladen haben.

Hier ist der Code:

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

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

Vergessen Sie nicht, die Methoden GetHashCode () und Equals () für Ihr Objekt zu überladen.

Dieser Beitrag hat mir geholfen: c # vergleiche zwei generische Werte

Sushil

Sushil
quelle
1
NB das gleiche Problem wie im Kommentar auf stackoverflow.com/questions/98033/… identifiziert - kann nicht annehmen, obj.GetHashCode () macht Sinn
Ruben Bartelink
4
Ich verstehe den Zweck dieses einen nicht. Sie haben einen Gleichheitsvergleich erstellt, der dem Standardgleichheitsvergleich entspricht. Warum benutzt du es nicht direkt?
CodesInChaos
1

Die Antwort von orip ist großartig. Die Antwort von orip erweitern:

Ich denke, dass der Schlüssel der Lösung die Verwendung der "Erweiterungsmethode" ist, um den "anonymen Typ" zu übertragen.

    public static class Comparer 
    {
      public static IEqualityComparer<T> CreateComparerForElements<T>(this IEnumerable<T> enumerable, Func<T, object> keyExtractor)
      {
        return new KeyEqualityComparer<T>(keyExtractor);
      }
    }

Verwendung:

var n = ItemList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList();
n.AddRange(OtherList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList(););
n = n.Distinct(x=>new{Vchr=x.Vchr,Id=x.Id}).ToList();
Matrix
quelle
0
public static Dictionary<TKey, TValue> Distinct<TKey, TValue>(this IEnumerable<TValue> items, Func<TValue, TKey> selector)
  {
     Dictionary<TKey, TValue> result = null;
     ICollection collection = items as ICollection;
     if (collection != null)
        result = new Dictionary<TKey, TValue>(collection.Count);
     else
        result = new Dictionary<TKey, TValue>();
     foreach (TValue item in items)
        result[selector(item)] = item;
     return result;
  }

Dies ermöglicht die Auswahl einer Eigenschaft mit Lambda wie folgt: .Select(y => y.Article).Distinct(x => x.ArticleID);

Max
quelle
-2

Ich kenne keine existierende Klasse, aber so etwas wie:

public class MyComparer<T> : IEqualityComparer<T>
{
  private Func<T, T, bool> _compare;
  MyComparer(Func<T, T, bool> compare)
  {
    _compare = compare;
  }

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

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

Hinweis: Ich habe dies noch nicht kompiliert und ausgeführt, daher liegt möglicherweise ein Tippfehler oder ein anderer Fehler vor.

Gregg
quelle
1
NB das gleiche Problem wie im Kommentar auf stackoverflow.com/questions/98033/… identifiziert - kann nicht annehmen, obj.GetHashCode () macht Sinn
Ruben Bartelink