Zawiń delegata w IEqualityComparer

127

Kilka funkcji Linq.Enumerable przyjmuje rozszerzenie IEqualityComparer<T>. Czy istnieje wygodna klasa opakowująca, która dostosowuje a delegate(T,T)=>booldo implementacji IEqualityComparer<T>? Łatwo jest go napisać (jeśli zignorujesz problemy ze zdefiniowaniem poprawnego hashcode), ale chciałbym wiedzieć, czy istnieje rozwiązanie gotowe do użycia.

W szczególności chcę wykonać operacje ustawiania na Dictionarys, używając tylko kluczy do definiowania członkostwa (zachowując wartości według różnych reguł).

Marcelo Cantos
źródło

Odpowiedzi:

44

Zwykle rozwiązuję to, komentując @Sam na odpowiedzi (dokonałem edycji oryginalnego postu, aby trochę go wyczyścić bez zmiany zachowania).

Poniżej znajduje się mój riff z odpowiedzi @ Sam , z krytyczną poprawką [IMNSHO] do domyślnej polityki mieszania: -

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
źródło
5
Jeśli o mnie chodzi, to jest poprawna odpowiedź. Wszystko, IEqualityComparer<T>co pomija GetHashCode, jest po prostu złamane.
Dan Tao,
1
@Joshua Frank: Nie można używać równości hash do sugerowania równości - tylko odwrotność jest prawdą. Krótko mówiąc, @Dan Tao ma całkowitą rację w tym, co mówi, a ta odpowiedź jest po prostu zastosowaniem tego faktu do wcześniej niekompletnej odpowiedzi
Ruben Bartelink,
2
@Ruben Bartelink: Dzięki za wyjaśnienie. Ale nadal nie rozumiem twojej polityki haszowania t => 0. Jeśli wszystkie obiekty zawsze haszują to samo (zero), to czy nie jest to nawet bardziej zepsute niż użycie obj.GetHashCode, według punktu @Dan Tao? Dlaczego nie zawsze zmusić rozmówcę do zapewnienia dobrej funkcji skrótu?
Joshua Frank
1
Dlatego nie jest rozsądne zakładanie, że dowolny algorytm w dostarczonej funkcji Func nie może w żaden sposób zwrócić prawdy pomimo różnych kodów skrótów. Twój pogląd, że zwracanie zera przez cały czas po prostu nie jest mieszaniem, jest prawdą. Dlatego istnieje przeciążenie, które pobiera haszującą Func, gdy profiler mówi nam, że wyszukiwania nie są wystarczająco wydajne. Jedynym punktem w tym wszystkim jest to, że jeśli zamierzasz mieć domyślny algorytm haszujący, powinien on działać w 100% przypadków i nie ma niebezpiecznego, powierzchownie poprawnego zachowania. A potem możemy popracować nad występem!
Ruben Bartelink
4
Innymi słowy, ponieważ używasz niestandardowej funkcji porównującej, nie ma to nic wspólnego z domyślnym kodem skrótu obiektu powiązanym z domyślną funkcją porównującą, dlatego nie możesz jej użyć.
Peet Brits
170

O znaczeniu GetHashCode

Inni już skomentowali fakt, że każda niestandardowa IEqualityComparer<T>implementacja powinna naprawdę zawierać GetHashCodemetodę ; ale nikt nie zadał sobie trudu, aby szczegółowo wyjaśnić dlaczego .

Dlatego. Twoje pytanie konkretnie wspomina o metodach rozszerzających LINQ; Prawie wszystkie z nich polegają na kodach skrótu, aby działać poprawnie, ponieważ wewnętrznie wykorzystują tabele skrótów w celu zwiększenia wydajności.

Weźmy Distinctna przykład. Rozważ konsekwencje tej metody rozszerzającej, jeśli wszystko, co wykorzystała, było Equalsmetodą. Jak ustalisz, czy element został już zeskanowany w sekwencji, jeśli tylko to zrobiłeś Equals? Wyliczasz wszystkie wartości, które już przeglądałeś, i sprawdzasz, czy są zgodne. Spowodowałoby to Distinctużycie algorytmu O (N 2 ) w najgorszym przypadku zamiast algorytmu O (N)!

Na szczęście tak nie jest. Distinctnie tylko używa Equals; używa GetHashCoderównież. W rzeczywistości absolutnie nie działa poprawnie bez IEqualityComparer<T>dostarczenia właściwegoGetHashCode . Poniżej znajduje się wymyślony przykład ilustrujący to.

Powiedzmy, że mam następujący 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);
    }
}

Teraz powiedz, że mam List<Value>i chcę znaleźć wszystkie elementy o różnych nazwach. Jest to doskonały przypadek Distinctużycia niestandardowego modułu porównującego równość. Skorzystajmy więc z Comparer<T>klasy z odpowiedzi Aku :

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

Teraz, jeśli mamy kilka Valueelementów o tej samej Namewłaściwości, wszystkie powinny zwinąć się w jedną wartość zwróconą przez Distinct, prawda? Zobaczmy...

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

Wynik:

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

Hmm, to nie zadziałało, prawda?

O co chodzi GroupBy? Spróbujmy tego:

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

Wynik:

[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

Ponownie: nie zadziałało.

Jeśli się nad tym zastanowić, sensowne Distinctbyłoby użycie a HashSet<T>(lub odpowiednika) wewnętrznie i GroupByużycie czegoś takiego jak Dictionary<TKey, List<T>>wewnętrznie. Czy to mogłoby wyjaśnić, dlaczego te metody nie działają? Spróbujmy tego:

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

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

Wynik:

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

Tak ... zaczyna to mieć sens?

Mamy nadzieję, że z tych przykładów jasno wynika, dlaczego uwzględnienie odpowiedniego rozwiązania GetHashCodew każdej IEqualityComparer<T>implementacji jest tak ważne.


Oryginalna odpowiedź

Rozszerzanie odpowiedzi Oripa :

Można tu wprowadzić kilka ulepszeń.

  1. Po pierwsze, wziąłbym Func<T, TKey>zamiast Func<T, object>; Zapobiegnie to umieszczaniu kluczy typu wartości w keyExtractorsamej rzeczywistości .
  2. Po drugie, właściwie dodałbym where TKey : IEquatable<TKey>ograniczenie; zapobiegnie to opakowaniu w Equalswywołaniu ( object.Equalspobiera objectparametr; potrzebujesz IEquatable<TKey>implementacji, aby pobrać TKeyparametr bez pakowania go w ramki). Oczywiście może to stanowić zbyt poważne ograniczenie, więc można utworzyć klasę bazową bez ograniczenia, a wraz z nią klasę pochodną.

Oto, jak może wyglądać wynikowy kod:

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
źródło
1
Twoja StrictKeyEqualityComparer.Equalsmetoda wydaje się być taka sama jak KeyEqualityComparer.Equals. Czy to TKey : IEquatable<TKey>ograniczenie sprawia, że TKey.Equalsdziała inaczej?
Justin Morgan
2
@JustinMorgan: Tak - w pierwszym przypadku, ponieważ TKeymoże to być dowolny typ, kompilator zastosuje metodę wirtualną, Object.Equalsktóra będzie wymagała pakowania parametrów typu wartości, np int. Jednak w tym drugim przypadku, ponieważ TKeyjego implementacja jest ograniczona IEquatable<TKey>, TKey.Equalszostanie użyta metoda, która nie będzie wymagała pakowania.
Dan Tao
2
Bardzo interesujące, dzięki za informację. Nie miałem pojęcia, że ​​GetHashCode ma te konsekwencje dla LINQ, dopóki nie zobaczyłem tych odpowiedzi. Dobrze wiedzieć do przyszłego użytku.
Justin Morgan
1
@JohannesH: Prawdopodobnie! Eliminowałoby też potrzebę StringKeyEqualityComparer<T, TKey>.
Dan Tao
1
+1 @DanTao: Spóźnione dzięki za świetne wyjaśnienie, dlaczego nigdy nie należy ignorować kodów skrótów podczas definiowania równości w .Net.
Marcelo Cantos
118

Jeśli chcesz dostosować sprawdzanie równości, w 99% przypadków jesteś zainteresowany zdefiniowaniem kluczy do porównania, a nie samym porównaniem.

To mogłoby być eleganckie rozwiązanie (koncepcja zaczerpnięta z metody sortowania listy w Pythonie ).

Stosowanie:

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

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

KeyEqualityComparerKlasa:

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
źródło
3
To znacznie lepsze niż odpowiedź Aku.
SLaks
Zdecydowanie właściwe podejście. Moim zdaniem można wprowadzić kilka ulepszeń, o których wspomniałem we własnej odpowiedzi.
Dan Tao,
1
To bardzo elegancki kod, ale nie odpowiada na pytanie, dlatego zamiast tego zaakceptowałem odpowiedź @ aku. Chciałem otoki dla Func <T, T, bool> i nie muszę wyodrębniać klucza, ponieważ klucz jest już oddzielony w moim słowniku.
Marcelo Cantos
6
@Marcelo: W porządku, możesz to zrobić; ale pamiętaj, że jeśli masz zamiar przyjąć podejście @ aku, naprawdę powinieneś dodać a, Func<T, int>aby dostarczyć kod skrótu dla Twartości (jak zasugerowano np. w odpowiedzi Rubena ). W przeciwnym razie IEqualityComparer<T>implementacja, z którą zostaniesz, jest dość zepsuta, szczególnie w odniesieniu do jej przydatności w metodach rozszerzających LINQ. Zobacz moją odpowiedź w dyskusji na temat tego, dlaczego tak jest.
Dan Tao,
To fajne, ale jeśli wybrany klucz byłby typem wartości, byłoby niepotrzebne opakowanie. Być może lepiej byłoby mieć TKey do definiowania klucza.
Graham Ambrose
48

Obawiam się, że nie ma takiego opakowania po wyjęciu z pudełka. Jednak stworzenie takiego nie jest trudne:

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
źródło
1
Należy jednak zachować ostrożność przy tej implementacji GetHashCode. Jeśli faktycznie zamierzasz używać go w jakiejś tabeli mieszania, będziesz potrzebować czegoś bardziej niezawodnego.
thecoop
46
ten kod ma poważny problem! łatwo jest wymyślić klasę, która ma dwa obiekty, które są równe pod względem tej funkcji porównującej, ale mają różne kody skrótu.
empi,
10
Aby temu zaradzić, klasa potrzebuje innego elementu członkowskiego, private readonly Func<T, int> _hashCodeResolverktóry również musi zostać przekazany w konstruktorze i użyty w GetHashCode(...)metodzie.
herzmeister
6
Jestem ciekawy: dlaczego używasz obj.ToString().ToLower().GetHashCode()zamiast obj.GetHashCode()?
Justin Morgan
3
Miejsca w frameworku, które IEqualityComparer<T>niezmiennie używają haszowania za kulisami (np. LINQ's GroupBy, Distinct, Except, Join, itp.) I umowa MS dotycząca haszowania są zepsute w tej implementacji. Oto fragment dokumentacji MS: „Implementacje są wymagane, aby zapewnić, że jeśli metoda Equals zwróci true dla dwóch obiektów x i y, to wartość zwrócona przez metodę GetHashCode dla x musi być równa wartości zwróconej dla y”. Zobacz: msdn.microsoft.com/en-us/library/ms132155
devgeezer
22

Taka sama jak odpowiedź Dana Tao, ale z kilkoma ulepszeniami:

  1. Polega na EqualityComparer<>.Defaultprzeprowadzaniu rzeczywistych porównań, aby uniknąć pakowania structzaimplementowanych typów wartości IEquatable<>.

  2. Odkąd jest EqualityComparer<>.Defaultużywany, nie eksploduje null.Equals(something).

  3. Dostarczone statyczne opakowanie, wokół IEqualityComparer<>którego będzie istniała statyczna metoda tworzenia instancji funkcji porównującej - ułatwia wywoływanie. Porównać

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

    z

    new EqualityComparer<Person, int>(p => p.ID);
  4. Dodano przeciążenie do określenia IEqualityComparer<>dla klucza.

Klasa:

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

możesz go używać w ten sposób:

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

Osoba to prosta klasa:

class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
}
ldp615
źródło
3
+1 za udostępnienie implementacji, która pozwala Ci na porównanie klucza. Oprócz większej elastyczności pozwala to również uniknąć typów wartości pudełkowych zarówno dla porównań, jak i haszowania.
devgeezer
2
Oto najbardziej szczegółowa odpowiedź. Dodałem również czek zerowy. Kompletny.
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 );
    }
}

Z rozszerzeniami: -

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
źródło
@Sam (który już nie istnieje od tego komentarza): wyczyszczono kod bez korygowania zachowania (i dano +1). Dodano Riff na stackoverflow.com/questions/98033/ ...
Ruben Bartelink
6

Odpowiedź oripa jest świetna.

Oto mała metoda rozszerzenia, aby było to jeszcze łatwiejsze:

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
źródło
2

Odpowiem na własne pytanie. Aby traktować słowniki jako zestawy, najprostszą metodą wydaje się być zastosowanie operacji na zestawach do dict.Keys, a następnie konwersja z powrotem do słowników za pomocą Enumerable.ToDictionary (...).

Marcelo Cantos
źródło
2

Implementacja w (tekst niemiecki) Implementing IEqualityCompare with lambda expression dba o wartości null i używa metod rozszerzających do generowania IEqualityComparer.

Aby utworzyć IEqualityComparer w unii Linq, wystarczy napisać

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

Osoba porównująca:

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

Musisz również dodać metodę rozszerzenia, aby obsługiwać wnioskowanie o typie

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));
       }
   }
Smażona
źródło
1

Tylko jedna optymalizacja: zamiast delegowania możemy użyć gotowego rozwiązania EqualityComparer do porównań wartości.

To również uczyniłoby implementację czystszą, ponieważ rzeczywista logika porównawcza pozostaje teraz w GetHashCode () i Equals (), które być może już zostały przeciążone.

Oto kod:

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

Nie zapomnij przeładować metod GetHashCode () i Equals () na swoim obiekcie.

Ten post mi pomógł: c # porównać dwie wartości ogólne

Sushil

Sushil
źródło
1
NB ten sam problem, który został zidentyfikowany w komentarzu do stackoverflow.com/questions/98033/ ... - CANT zakładam, że obj.GetHashCode () ma sens
Ruben Bartelink
4
Nie rozumiem celu tego. Utworzono moduł porównujący równość, który jest równoważny z domyślną funkcją porównującą równość. Dlaczego więc nie używasz go bezpośrednio?
CodesInChaos
1

Odpowiedź oripa jest świetna. Rozszerzanie odpowiedzi Oripa:

Myślę, że kluczem do rozwiązania jest użycie „metody rozszerzenia”, aby przenieść „typ anonimowy”.

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

Stosowanie:

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();
matryca
źródło
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;
  }

Dzięki temu można wybrać właściwość z lambdą w następujący sposób: .Select(y => y.Article).Distinct(x => x.ArticleID);

Maks
źródło
-2

Nie wiem o istniejącej klasie, ale coś takiego:

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

Uwaga: tak naprawdę jeszcze tego nie skompilowałem i nie uruchomiłem, więc może być literówka lub inny błąd.

Gregg
źródło
1
NB ten sam problem, który został zidentyfikowany w komentarzu do stackoverflow.com/questions/98033/ ... - CANT zakładam, że obj.GetHashCode () ma sens
Ruben Bartelink