2015-08-07 30 views
7

Gdy otrzymujesz klucz ze Słownika, ale nie masz pewności, zwykle używasz TryGetValue zamiast ContainsKey + programu pobierającego indeks, aby uniknąć podwójnego kliknięcia klucza. Innymi słowy, to:Najlepszy sposób sprawdzenia, czy klucz istnieje w słowniku przed dodaniem?

string password; 
if (accounts.TryGetValue(username, out password)) 
{ 
    // use the password 
} 

będzie wolał to:

if (accounts.ContainsKey(username)) 
{ 
    string password = accounts[username]; 
} 

Co jeśli chciałem sprawdzić, czy klucz istniała już przed ustawieniem go na wartości? Na przykład, chciałbym sprawdzić, czy nazwa istniała przed zastąpieniem go nowym hasłem:

if (!accounts.ContainsKey(username)) 
{ 
    accounts.Add(username, password); 
} 
else 
{ 
    Console.WriteLine("Username is taken!"); 
} 

vs

// this doesn't exist 
if (!accounts.TrySetValue(username, password)) 
{ 
    Console.WriteLine("Username is taken!"); 
} 

jest tam więcej wydajnych alternatywą ContainsKey i Add że robi to ?

+0

var hasło = keyValue.Where (entry => entry.Key.Zawiera ("hasło")) . Wybierz (element => item.Key) .FirstOrDefault(); –

+1

@SanjeevS Dziękuję za odpowiedź, ale LINQ byłby o wiele bardziej nieefektywny niż rozwiązanie awaryjne, które napisałem powyżej. –

+0

Nie jestem pewien, czy można tu wiele zyskać, ponieważ słownik jest wewnętrznie oparty na tabeli mieszania, tak że uzyskanie wartości kluczem (np. Za pomocą indeksera) ma złożoność O (1). – Grx70

Odpowiedz

5

Jeśli nie chcesz przesłonić, myślę, że lepiej napisać własną metodę rozszerzenia, taką jak TryGetValue. Nie ma standardowej metody.

LUB

Zastosowanie CuncurrentDictionary, ma TryAdd metody, ale musisz narzut na synchronizacji.

Tak, prosta odpowiedź - nie, nie ma takiej metody.

+1

Nie można zapisać takiej metody rozszerzenia, chyba że masz dostęp do prywatnych członków 'Dictionary'. –

+0

@JamesKo jakich prywatnych metod potrzebujesz? Masz ContainsKey. To wystarczy – Backs

+1

Backs, to pytanie dotyczy perf. Jeśli wywołasz 'ContainsKey', a następnie' Add', sprawdzasz, czy klucz istnieje dwa razy. Właśnie dlatego istnieje "TryGetValue" i dlatego nie spełnia tego pytania. –

3

Mam tendencję do pisania własnych rozszerzeń w razie potrzeby.

Na przykład GetValueOrDefault tak:

public static V GetValueOrDefault<K, V>(this IDictionary<K, V> @this, K key, Func<V> @default) 
{ 
    return @this.ContainsKey(key) ? @this[key] : @default(); 
} 

Może być stosowany tak:

var password = accounts.GetValueOrDefault(username,() => null); 
if (password != null) 
{ 
    //do stuff 
} 

Albo SetValueIfExists:

public static V SetValueIfExists<K, V>(this IDictionary<K, V> @this, K key, V value) 
{ 
    if (@this.ContainsKey(key)) 
    { 
     @this[key] = value; 
    } 
} 

Albo SetValueIfNotExists:

public static V SetValueIfNotExists<K, V>(this IDictionary<K, V> @this, K key, V value) 
{ 
    if ([email protected](key)) 
    { 
     @this[key] = value; 
    } 
} 
+1

TS chce SetValue lub default, a nie GetValue .. –

+0

@Enigmativity Haha dziękuję, ale jeśli zauważysz, że twoja metoda rozszerzenia jest dokładnie tym samym, o którym wspomniałem w moim pytaniu. –

+0

@JamesKo - Tak, ale jako metoda rozszerzenia sprawia, że ​​jego użycie jest lepsze. Czy nie tego właśnie chciałeś? A może chciałeś coś wypalić w języku? – Enigmativity

4

Jeśli sądzisz, że wstawianie nowej nazwy będzie powszechne, a próba wstawienia duplikatu będzie rzadkim przypadkiem, możesz po prostu chcieć wykorzystać obciążenie związane z przechwyceniem wyjątku.

try 
{ 
    accounts.Add(username, password); 
} 
catch (ArgumentException) 
{ 
    Console.WriteLine("Username is taken!"); 
} 

Jeśli zadzwonisz Add z istniejącym Klucz ArgumentException zostanie wyrzucony. Nawet jeśli masz częste duplikaty, będzie to prawdopodobnie bardziej wydajne niż twoje sprawdzanie ContainsKey.

+1

Liczby w tej [odpowiedzi] (https://stackoverflow.com/a/13194904/804797) pokazują, że jeśli "rzadki przypadek" zdarza się więcej niż 0,16% czasu (1 na każde 625), wolniej jest po prostu try-catch i staje się znacznie gorszy z większą liczbą wyjątków, podczas gdy kontrola ContainsKey doda nieznaczny narzut do ** najbardziej ** kodu. Również ... Jeśli istnieje prosty sposób na uniknięcie wyjątków w regularnym przepływie programu, zawsze jest to preferowana praktyka. Zobacz odczyty: https://msdn.microsoft.com/en-us/library/ms229009(v=vs.100).aspx, https://stackoverflow.com/a/891230/804797, https: // stackoverflow. com/a/161965/804797 – Arkaine55

0

Wiem, że się spóźniłem, ale można użyć lewy i zapisać liczbę przed ustawieniem indeksu i sprawdzić liczbę po ustawieniu indeksera. Jeśli liczba jest taka sama, zastąpiłeś klucz, w przeciwnym razie dodałeś nowe mapowanie:

public static bool AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> 
    dictionary, TKey key, TValue value) 
{ 
    var countBefore = dictionary.Count; 
    dictionary[key] = value; 
    return countBefore != dictionary.Count; 
} 
+0

Interesujący pomysł. Należy jednak zauważyć, że nie pomaga to w "zwykłym" przypadku, czyli sprawdzaniu, czy istnieje, ponieważ * nie * chcesz zastąpić istniejącego wpisu. LUB jeśli nadpisujesz, chcesz zwrócić nadpisaną wartość (np. Musisz usunąć stare). W drugim przypadku 'AddOrUpdate' zwróci' TValue' i wykona: 'var valueBefore;' 'dictionary.TryGetValue (key, out value);' 'dictionary [key] = value;' 'return valueBefore;' Of Oczywiście, ma to wpływ, który James Ko próbował uniknąć, wspominając o tym, że jest to przydatna alternatywa. – ToolmakerSteve

+0

To prawda (przeczytałem tylko tytuł). Myślisz, że zamierzałeś użyć valueBefore w TryGet ... – MaximGurschi