Tytuł jest wystarczająco prosty, dlaczego nie mogę:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Tytuł jest wystarczająco prosty, dlaczego nie mogę:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Odpowiedzi:
Komentarz do pierwotnego pytania podsumowuje to całkiem dobrze:
ponieważ nikt nigdy nie zaprojektował, nie wyszczególnił, nie wdrożył, nie przetestował, nie udokumentował ani nie dostarczył tej funkcji. - @Gabe Moothart
A dlaczego? Cóż, prawdopodobnie dlatego, że nie można uzasadnić zachowania scalania słowników w sposób zgodny z wytycznymi Framework.
AddRange
nie istnieje, ponieważ zakres nie ma żadnego znaczenia dla kontenera asocjacyjnego, ponieważ zakres danych pozwala na zduplikowane wpisy. Np. Jeśli masz taką IEnumerable<KeyValuePair<K,T>>
kolekcję, nie chroni przed zduplikowanymi wpisami.
Zachowanie polegające na dodaniu kolekcji par klucz-wartość lub nawet scaleniu dwóch słowników jest proste. Jednak nie jest to sposób postępowania z wieloma zduplikowanymi wpisami.
Jakie powinno być zachowanie metody w przypadku duplikatu?
Są co najmniej trzy rozwiązania, które przychodzą mi do głowy:
Jaki powinien być stan oryginalnego słownika po zgłoszeniu wyjątku?
Add
jest prawie zawsze implementowana jako operacja atomowa: kończy się powodzeniem i aktualizuje stan kolekcji lub kończy się niepowodzeniem, a stan kolekcji pozostaje niezmieniony. Ponieważ AddRange
może się nie powieść z powodu zduplikowanych błędów, sposobem zachowania spójności z jego zachowaniem Add
byłoby również uczynienie go niepodzielnym przez rzucenie wyjątku na dowolny duplikat i pozostawienie stanu oryginalnego słownika bez zmian.
Jako konsument interfejsu API byłoby żmudne iteracyjne usuwanie zduplikowanych elementów, co oznacza, że AddRange
powinien zgłosić pojedynczy wyjątek zawierający wszystkie zduplikowane wartości.
Wybór sprowadza się wówczas do:
Istnieją argumenty za wsparciem obu przypadków użycia. Aby to zrobić, czy dodajesz IgnoreDuplicates
flagę do podpisu?
IgnoreDuplicates
Flag (gdy wartość true) będzie również zapewniać znaczną prędkość w górę, jako podstawowej implementacji ominie kodzie duplikatu kontroli.
Więc teraz masz flagę, która pozwala na AddRange
obsługę obu przypadków, ale ma nieudokumentowany efekt uboczny (nad którym projektanci Framework naprawdę ciężko pracowali).
Podsumowanie
Ponieważ nie ma jasnego, spójnego i oczekiwanego zachowania, jeśli chodzi o postępowanie z duplikatami, łatwiej jest nie zajmować się nimi wszystkimi razem i nie zapewnić metody na początek.
Jeśli ciągle będziesz musiał scalać słowniki, możesz oczywiście napisać własną metodę rozszerzenia, aby scalić słowniki, które będą zachowywać się w sposób odpowiedni dla Twojej aplikacji (-ów).
AddMultiple
różni się od AddRange
tego, niezależnie od tego, czy jego implementacja będzie wątpliwa: czy rzucasz wyjątek z tablicą wszystkich zduplikowanych kluczy? A może rzucasz wyjątek na pierwszy napotkany duplikat klucza? Jaki powinien być stan słownika, jeśli zostanie zgłoszony wyjątek? Nieskazitelny, czy wszystkie klucze, którym się udało?
Add
- albo zawiń każdą Add
w a try...catch
i złap w ten sposób duplikaty; lub użyj indeksatora i nadpisz pierwszą wartość późniejszą; lub zapobiegawczo sprawdź użycie ContainsKey
przed próbą Add
, zachowując w ten sposób oryginalną wartość. Gdyby struktura miała metodę AddRange
lub AddMultiple
, jedynym prostym sposobem przekazania informacji o tym, co się stało, byłby wyjątek, a obsługa i odzyskiwanie danych byłyby nie mniej skomplikowane.
Mam rozwiązanie:
Dictionary<string, string> mainDic = new Dictionary<string, string>() {
{ "Key1", "Value1" },
{ "Key2", "Value2.1" },
};
Dictionary<string, string> additionalDic= new Dictionary<string, string>() {
{ "Key2", "Value2.2" },
{ "Key3", "Value3" },
};
mainDic.AddRangeOverride(additionalDic); // Overrides all existing keys
// or
mainDic.AddRangeNewOnly(additionalDic); // Adds new keys only
// or
mainDic.AddRange(additionalDic); // Throws an error if keys already exist
// or
if (!mainDic.ContainsKeys(additionalDic.Keys)) // Checks if keys don't exist
{
mainDic.AddRange(additionalDic);
}
...
namespace MyProject.Helper
{
public static class CollectionHelper
{
public static void AddRangeOverride<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => dic[x.Key] = x.Value);
}
public static void AddRangeNewOnly<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => { if (!dic.ContainsKey(x.Key)) dic.Add(x.Key, x.Value); });
}
public static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => dic.Add(x.Key, x.Value));
}
public static bool ContainsKeys<TKey, TValue>(this IDictionary<TKey, TValue> dic, IEnumerable<TKey> keys)
{
bool result = false;
keys.ForEachOrBreak((x) => { result = dic.ContainsKey(x); return result; });
return result;
}
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
action(item);
}
public static void ForEachOrBreak<T>(this IEnumerable<T> source, Func<T, bool> func)
{
foreach (var item in source)
{
bool result = func(item);
if (result) break;
}
}
}
}
Baw się dobrze.
ToList()
, słownik to plik IEnumerable<KeyValuePair<TKey,TValue>
. Ponadto druga i trzecia metoda metody zostanie wyświetlona, jeśli dodasz istniejącą wartość klucza. Nie jest to dobry pomysł, szukałeś TryAdd
? Ostatecznie drugiego można zastąpićWhere(pair->!dic.ContainsKey(pair.Key)...
ToList()
to nie jest dobre rozwiązanie, więc zmieniłem kod. Możesz użyć, try { mainDic.AddRange(addDic); } catch { do something }
jeśli nie jesteś pewien trzeciej metody. Druga metoda działa idealnie.
W przypadku, gdy ktoś natknie się na to pytanie tak jak ja - możliwe jest osiągnięcie "AddRange" za pomocą metod rozszerzających IEnumerable:
var combined =
dict1.Union(dict2)
.GroupBy(kvp => kvp.Key)
.Select(grp => grp.First())
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Główną sztuczką podczas łączenia słowników jest radzenie sobie ze zduplikowanymi kluczami. W powyższym kodzie jest to część .Select(grp => grp.First())
. W tym przypadku po prostu pobiera pierwszy element z grupy duplikatów, ale w razie potrzeby można tam zaimplementować bardziej wyrafinowaną logikę.
dict1
nie używa domyślnej funkcji porównującej równość?
var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
Domyślam się, że użytkownik nie ma odpowiednich informacji o tym, co się stało. Ponieważ w słownikach nie można mieć powtarzających się kluczy, jak poradziłbyś sobie z łączeniem dwóch słowników, w których krzyżują się niektóre klucze? Oczywiście można powiedzieć: „Nie obchodzi mnie to”, ale to łamie konwencję zwracania fałszu / wyrzucania wyjątku dla powtarzających się kluczy.
Add
, poza tym, że może się to zdarzyć więcej niż raz. Rzuciłby to samo ArgumentException
, Add
co na pewno?
NamedElementException
??), albo wyrzucony zamiast lub jako wewnętrzny wyjątek ArgumentException, który określa nazwany element, który powoduje konflikt ... kilka różnych opcji, powiedziałbym
Mógłbyś to zrobić
Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);
public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
dic.Add(.., ..);
}
lub użyj listy do dodawania i / lub używając powyższego wzorca.
List<KeyValuePair<string, string>>
MethodThatReturnAnotherDic
. Pochodzi z PO. Proszę ponownie przejrzeć pytanie i moją odpowiedź.
Jeśli masz do czynienia z nowym słownikiem (i nie masz istniejących wierszy do stracenia), zawsze możesz użyć ToDictionary () z innej listy obiektów.
Więc w twoim przypadku zrobiłbyś coś takiego:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
Dictionary<string, string> dic = SomeList.ToDictionary...
Jeśli wiesz, że nie będziesz mieć zduplikowanych kluczy, możesz:
dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Zgłosi wyjątek, jeśli istnieje zduplikowana para klucz / wartość.
Nie wiem, dlaczego nie ma tego w ramach; Powinien być. Nie ma niepewności; po prostu wyrzuć wyjątek. W przypadku tego kodu zgłasza wyjątek.
var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);
?
Zapraszam do korzystania z takiej metody rozszerzenia:
public static Dictionary<T, U> AddRange<T, U>(this Dictionary<T, U> destination, Dictionary<T, U> source)
{
if (destination == null) destination = new Dictionary<T, U>();
foreach (var e in source)
destination.Add(e.Key, e.Value);
return destination;
}
Oto alternatywne rozwiązanie wykorzystujące C # 7 ValueTuples (literały krotki)
public static class DictionaryExtensions
{
public static Dictionary<TKey, TValue> AddRange<TKey, TValue>(this Dictionary<TKey, TValue> source, IEnumerable<ValueTuple<TKey, TValue>> kvps)
{
foreach (var kvp in kvps)
source.Add(kvp.Item1, kvp.Item2);
return source;
}
public static void AddTo<TKey, TValue>(this IEnumerable<ValueTuple<TKey, TValue>> source, Dictionary<TKey, TValue> target)
{
target.AddRange(source);
}
}
Używane jak
segments
.Zip(values, (s, v) => (s.AsSpan().StartsWith("{") ? s.Trim('{', '}') : null, v))
.Where(zip => zip.Item1 != null)
.AddTo(queryParams);
Jak wspominali inni, powodem, dla którego Dictionary<TKey,TVal>.AddRange
nie został zaimplementowany, jest to, że istnieje wiele sposobów obsługi przypadków, w których masz duplikaty. Jest to również przypadek Collection
lub interfejsy, takie jak IDictionary<TKey,TVal>
, ICollection<T>
itp
Tylko List<T>
implementuje to, a zauważysz, że IList<T>
interfejs nie działa z tych samych powodów: oczekiwane zachowanie podczas dodawania zakresu wartości do kolekcji może się znacznie różnić w zależności od kontekstu.
Kontekst twojego pytania sugeruje, że nie martwisz się o duplikaty, w takim przypadku masz prostą alternatywę oneliner, używając Linq:
MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));