Owinąć delegata w Ieequalitycomparer
Kilka Linq.Funkcje wyliczeniowe przyjmują IEqualityComparer<T>
. Czy istnieje wygodna Klasa wrapper, która dostosowuje delegate(T,T)=>bool
do implementacji IEqualityComparer<T>
? Łatwo jest go napisać (jeśli masz problemy z zdefiniowaniem poprawnego hashcode), ale chciałbym wiedzieć, czy jest gotowe rozwiązanie.
W szczególności chcę wykonywać operacje na Dictionary
s, używając tylko kluczy do zdefiniowania członkostwa (zachowując wartości zgodnie z różnymi zasadami).
13 answers
Normalnie, chciałbym to rozwiązać, komentując @Sam na odpowiedź (zrobiłem trochę edycji na oryginalnym poście, aby oczyścić go trochę bez zmiany zachowania.)
Oto mój riff z @ Sam ' s answer, z [IMNSHO] krytyczną poprawką do domyślnej polityki haszowania:-
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 );
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:19
O znaczeniu GetHashCode
Inni już skomentowali fakt, że każda implementacja IEqualityComparer<T>
powinna zawierać metodę GetHashCode
{47]}; ale nikt nie zadaje sobie trudu, aby wyjaśnić {48]} dlaczego {49]} w jakikolwiek sposób.
Take Distinct
, na przykład. Rozważmy implikacje tej metody rozszerzenia, jeśli wszystkie wykorzystane były metodą Equals
. Jak określić, czy element został już zeskanowany w sekwencji, jeśli masz tylko Equals
? Wyliczasz na całym zbiorze wartości, na które już spojrzałeś i sprawdzasz dopasowanie. Wynikałoby to z Distinct
użycia najgorszego przypadku O (N2) algorytm zamiast O (N)!
Distinct
Nie tylko używać Equals
; to używa również GetHashCode
. W rzeczywistości, to absolutnie nie działa prawidłowo bez IEqualityComparer<T>
, który dostarcza WŁAŚCIWEGO GetHashCode
. 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 powiedzmy, że mam List<Value>
i chcę znaleźć wszystkie elementy o odrębnej nazwie. Jest to idealny przypadek użycia Distinct
przy użyciu niestandardowego porównywania równości. Więc użyjmy Comparer<T>
klasy z odpowiedzi Aku :
var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);
Teraz, jeśli mieć kilka Value
elementów o tej samej właściwości Name
, Wszystkie powinny się zwinąć w jedną wartość zwracaną 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);
}
Wyjście:
x: 1346013431 x: 1388845717 x: 1576754134 x: 1104067189 x: 1144789201 x: 1862076501 x: 1573781440 x: 646797592 x: 655632802 x: 1206819377Nie zadziałało, prawda?
A co z GroupBy
? Spróbujmy:
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);
}
}
Wyjście:
[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: 1206819377Znowu: nie zadziałało.
Jeśli się nad tym zastanowić, byłoby sensowne, aby Distinct
użyć HashSet<T>
(lub równoważnego) wewnętrznie, a GroupBy
użyć czegoś w rodzaju a Dictionary<TKey, List<T>>
wewnętrznie. Czy to może wyjaśnić, dlaczego te metody nie działają? Spróbujmy:
var uniqueValues = new HashSet<Value>(values, comparer);
foreach (Value x in uniqueValues)
{
Console.WriteLine(x);
}
Wyjście:
x: 1346013431 x: 1388845717 x: 1576754134 x: 1104067189 x: 1144789201 x: 1862076501 x: 1573781440 x: 646797592 x: 655632802 x: 1206819377Tak... zaczyna to mieć sens?
Mam nadzieję, że z tych przykładów jasno wynika, dlaczego włączenie odpowiedniej GetHashCode
do dowolnej implementacji IEqualityComparer<T>
jest tak ważne.
Oryginalna odpowiedź
Jest kilka ulepszeń, które można tutaj wprowadzić.
- Po Pierwsze, chciałbym należy użyć
Func<T, TKey>
zamiastFunc<T, object>
; zapobiegnie to umieszczaniu kluczy typu value w samymkeyExtractor
. - po drugie, dodałbym ograniczenie
where TKey : IEquatable<TKey>
; zapobiegnie to boks w wywołaniuEquals
(object.Equals
przyjmuje parametrobject
; potrzebujesz implementacjiIEquatable<TKey>
, aby pobrać parametrTKey
bez boksu). Oczywiście może to stanowić zbyt poważne ograniczenie, więc można utworzyć klasę bazową bez ograniczenia i klasę pochodną z nim.
Oto co wynikowy kod może wyglądać następująco:
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));
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:14
Gdy chcesz dostosować sprawdzanie równości, w 99% przypadków interesuje Cię zdefiniowanie kluczy do porównania, a nie samo porównanie.
To może być eleganckie rozwiązanie (koncepcja z metody sortowania list w Pythonie ).
Użycie:
var foo = new List<string> { "abc", "de", "DE" };
// case-insensitive distinct
var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );
Klasa KeyEqualityComparer
:
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();
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-06 14:41:23
Obawiam się, że nie ma takiego pudełka. Jednak nie jest trudno go stworzyć:
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));
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-18 23:52:01
Tak samo jak odpowiedź dana Tao, ale z kilkoma ulepszeniami:
Opiera się na
EqualityComparer<>.Default
, aby dokonać rzeczywistego porównania, aby uniknąć boksowania dla typów wartości (struct
s), które zaimplementowałyIEquatable<>
.Ponieważ
EqualityComparer<>.Default
używane nie eksploduje nanull.Equals(something)
.-
Pod warunkiem statycznego wrappera wokół
IEqualityComparer<>
, który będzie miał statyczną metodę do tworzenia instancji wywołania comparer - eases. PorównajEquality<Person>.CreateComparer(p => p.ID);
Z
new EqualityComparer<Person, int>(p => p.ID);
Dodano przeciążenie, aby określić
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 następujący 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 jest prostą klasą:
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-15 14:56:18
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 ) );
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-15 16:10:38
Odpowiedź Oripa jest świetna.
Oto mała metoda rozszerzenia, aby było jeszcze łatwiej:
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())
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-27 09:44:57
Odpowiem na własne pytanie. Aby traktować Słowniki jako zestawy, najprostszą metodą wydaje się stosowanie operacji set do dict.Klawiszy, a następnie konwertować z powrotem do słowników z Enumerable.ToDictionary(...).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-19 00:09:28
Implementacja at (tekst niemiecki) implementacja Ieequalitycompare with lambda expression dba o wartości null i używa metod rozszerzeń do generowania Ieequalitycomparer.
Aby utworzyć IEqualityComparer w LINQ union wystarczy napisać
persons1.Union(persons2, person => person.LastName)
The comparer:
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 do obsługi wnioskowania typu
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));
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-29 11:26:55
Tylko jedna optymalizacja: Możemy użyć gotowego narzędzia EqualityComparer do porównywania wartości, zamiast delegowania go.
To również uczyniłoby implementację czystszą, ponieważ faktyczna logika porównywania pozostaje teraz w GetHashCode() i Equals (), które mogły być już 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 przeciążyć metod GetHashCode() i Equals () na swoim obiekcie.
Ten post mi pomógł: c # compare two generic wartości
Sushil
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:43
Odpowiedź Oripa jest świetna. Rozwiń odpowiedź oripa:
Myślę, że kluczem rozwiązania jest użycie " metody rozszerzenia "do przeniesienia"anonimowego typu".
public static class Comparer
{
public static IEqualityComparer<T> CreateComparerForElements<T>(this IEnumerable<T> enumerable, Func<T, object> keyExtractor)
{
return new KeyEqualityComparer<T>(keyExtractor);
}
}
Użycie:
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();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:27
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;
}
Umożliwia to wybór właściwości z lambda w następujący sposób: .Select(y => y.Article).Distinct(x => x.ArticleID);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-10 10:15:41
Nie znam istniejącej klasy, ale coś w stylu:
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: jeszcze tego nie skompilowałem i nie uruchomiłem, więc może być jakaś literówka lub inny błąd.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-18 23:55:44