2013-05-07 9 views
7

Mam słownik, w którym kluczem jest Tuple, gdzie pierwszym elementem jest Data, a drugim elementem jest ciąg. Chciałbym, żeby słownik był niewrażliwy na wielkość liter.Niewrażliwy na przypadek słownik z kluczem Tuple

Wiem, że jeśli klucz był tylko ciągiem, mógłbym przekazać StringComparer.OrdinalIgnoreCase jako parametr przy deklarowaniu słownika, ale nie wydaje się, aby działało, gdy klucz jest krotką.

Czy jest jakiś sposób określenia StringComparer do użycia na drugim punkcie Tuple?

Dzięki

Odpowiedz

13

Stosować this overload konstruktora Dictionary, która pozwala określić niestandardową porównywarka na klawiszach. Można by towarzyszyć to z tworzeniem klasy, która implementuje

IEqualityComparer<Tuple<string, DateTime>> 

Który może wyglądać następująco:

class CustomEqualityComparer : IEqualityComparer<Tuple<string, DateTime>> 
{ 

    public bool Equals(Tuple<string, DateTime> lhs, Tuple<string, DateTime> rhs) 
    { 
     return 
      StringComparer.CurrentCultureIgnoreCase.Equals(lhs.Item1, rhs.Item1) 
     && lhs.Item2 == rhs.Item2; 
    } 


    public int GetHashCode(Tuple<string, DateTime> tuple) 
    { 
     return StringComparer.CurrentCultureIgnoreCase.GetHashCode(tuple.Item1) 
      ^tuple.Item2.GetHashCode(); 
    } 
} 

nie sprawdza argumentów tutaj, więc proszę nie traktować tego jako kod produkcji. Należy również zachować ostrożność, aby implementacje Equals i GetHashCode były zgodne z najważniejszym warunkiem, że , jeśli dwie krotki są takie same, muszą mieć ten sam kod skrótu. W przypadku porównywania niestandardowych tekstów łatwo jest wprowadzić błędy, jeśli nie są one szczególnie ostrożne: na przykład użycie ToLowerInvariant zamiast powyższego ToLower byłoby błędem (choć może nie pojawić się przez jakiś czas).

+0

Stosując tę ​​samą StringComparer do sprawdzania równości i uzyskania kod skrótu zazwyczaj działa najlepiej, aby uniknąć błędów, o których wspomniałeś. Edytowałem odpowiedź z proponowaną poprawką; proszę cofnąć, jeśli ci się nie podoba. – dtb

+0

@dtb: Zdecydowanie nie będzie cofania, ponieważ jest to poprawa spójności również IMO. Dzięki! – Jon

1

Ponieważ porównania nie będą uwzględniać wielkości liter, możesz użyć metody toLower/toUpper po stronie napisu podczas tworzenia krotek, a następnie zawsze niższych lub wyższych wartości łańcuchów, które będziesz mieć w krotkach używanych do pobierania/porównaj wpisy w słowniku.

+1

Porównywanie ciągów znaków przez ich konwersję na wielkie lub małe litery nie działa poprawnie w niektórych kulturach. Lepiej jest użyć StringComparer, takiego jak StringComparer.CurrentCultureIgnoreCase – dtb

0

Musiałem to w Dictionary<Tuple<>> owijki, więc użyłem kodu @Jon „s stworzyć generyczną wersję

public class TupleEqualityComparer<T1, T2> : IEqualityComparer<Tuple<T1, T2>> 
{ 
    private IEqualityComparer<T1> comparer1; 
    private IEqualityComparer<T2> comparer2; 

    public TupleEqualityComparer(IEqualityComparer<T1> comparer1, IEqualityComparer<T2> comparer2) 
    { 
     this.comparer1 = comparer1 ?? EqualityComparer<T1>.Default; 
     this.comparer2 = comparer2 ?? EqualityComparer<T2>.Default; 
    } 

    public bool Equals(Tuple<T1, T2> lhs, Tuple<T1, T2> rhs) 
    { 
     return comparer1.Equals(lhs.Item1, rhs.Item1) && comparer2.Equals(lhs.Item2, rhs.Item2); 
    } 

    public int GetHashCode(Tuple<T1, T2> tuple) 
    { 
     return comparer1.GetHashCode(tuple.Item1)^comparer2.GetHashCode(tuple.Item2); 
    } 

} 

public class Dictionary<TKey1, TKey2, TValue> : Dictionary<Tuple<TKey1, TKey2>, TValue>() 
{ 
    public Dictionary() : base() { } 
    public Dictionary(IEqualityComparer<TKey1> comparer1, IEqualityComparer<TKey2> comparer2) : base(new TupleEqualityComparer<TKey1, Tkey2>(comparer1, comparer2) { } 

    public TValue this[TKey1 key1, TKey2 key2] 
    { 
     get { return base[Tuple.Create(key1, key2)]; } 
    } 

    public void Add(TKey1 key1, TKey2 key2, TValue value) 
    { 
     base.Add(Tuple.Create(key1, key2), value); 
    } 

    public bool ContainsKey(TKey1 key1, TKey2 key2) 
    { 
     return base.ContainsKey(Tuple.Create(key1, key2)); 
    } 

    public bool TryGetValue(TKey1 key1, TKey2 key2, out TValue value) 
    { 
     return base.TryGetValue(Tuple.Create(key1, key2), out value); 
    } 
} 

Wykorzystanie

var dict = new Dictionary<string, DateTime, int>(
    StringComparer.OrdinalIgnoreCase, null); 
dict.Add("value1", DateTime.Now, 123); 
Assert.IsTrue(dict.ContainsKey("VALUe1"));