Il titolo è abbastanza semplice, perché non posso:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Il titolo è abbastanza semplice, perché non posso:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Risposte:
Un commento alla domanda originale lo riassume abbastanza bene:
perché nessuno ha mai progettato, specificato, implementato, testato, documentato e spedito quella funzione. - @Gabe Moothart
E perché? Bene, probabilmente perché il comportamento di unire i dizionari non può essere ragionato in un modo che si adatti alle linee guida del Framework.
AddRange
non esiste perché un intervallo non ha alcun significato per un contenitore associativo, poiché l'intervallo di dati consente voci duplicate. Ad esempio, se si IEnumerable<KeyValuePair<K,T>>
dispone di una raccolta, non si proteggono le voci duplicate.
Il comportamento di aggiungere una raccolta di coppie chiave-valore o anche di unire due dizionari è semplice. Il comportamento di come gestire più voci duplicate, tuttavia, non lo è.
Quale dovrebbe essere il comportamento del metodo quando si tratta di un duplicato?
Ci sono almeno tre soluzioni a cui riesco a pensare:
Quando viene generata un'eccezione, quale dovrebbe essere lo stato del dizionario originale?
Add
è quasi sempre implementato come operazione atomica: riesce e aggiorna lo stato della raccolta, oppure fallisce e lo stato della raccolta rimane invariato. Poiché AddRange
può fallire a causa di errori duplicati, il modo per mantenere il suo comportamento coerente con Add
sarebbe anche renderlo atomico lanciando un'eccezione su qualsiasi duplicato e lasciare invariato lo stato del dizionario originale.
Come consumatore di API, sarebbe noioso dover rimuovere in modo iterativo gli elementi duplicati, il che implica che AddRange
dovrebbe generare una singola eccezione che contiene tutti i valori duplicati.
La scelta quindi si riduce a:
Ci sono argomenti per supportare entrambi i casi d'uso. Per farlo, aggiungi un IgnoreDuplicates
flag alla firma?
Il IgnoreDuplicates
flag (se impostato su true) fornirebbe anche una notevole velocità, poiché l'implementazione sottostante ignorerebbe il codice per il controllo dei duplicati.
Quindi ora, hai un flag che consente AddRange
di supportare entrambi i casi, ma ha un effetto collaterale non documentato (che è qualcosa che i progettisti del Framework hanno lavorato davvero duramente per evitare).
Sommario
Poiché non esiste un comportamento chiaro, coerente e previsto quando si tratta di gestire i duplicati, è più facile non affrontarli tutti insieme e non fornire il metodo con cui iniziare.
Se ti ritrovi a dover continuamente unire dizionari, puoi ovviamente scrivere il tuo metodo di estensione per unire i dizionari, che si comporterà in un modo che funziona per le tue applicazioni.
AddMultiple
è diverso da AddRange
, indipendentemente dalla sua implementazione sarà traballante: lanci un'eccezione con un array di tutte le chiavi duplicate? O lanci un'eccezione sulla prima chiave duplicata che incontri? Quale dovrebbe essere lo stato del dizionario se viene generata un'eccezione? Pristine, o tutte le chiavi che sono riuscite?
Add
: avvolgere ciascuno Add
in una try...catch
e catturare i duplicati in questo modo; oppure utilizzare l'indicizzatore e sovrascrivere il primo valore con il valore successivo; o controllare preventivamente l'utilizzo ContainsKey
prima di tentare Add
, preservando così il valore originale. Se il framework avesse un metodo AddRange
or AddMultiple
, l'unico modo semplice per comunicare ciò che è accaduto sarebbe tramite un'eccezione, e la gestione e il recupero coinvolti non sarebbero meno complicati.
Ho qualche soluzione:
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;
}
}
}
}
Divertiti.
ToList()
, un dizionario è un file IEnumerable<KeyValuePair<TKey,TValue>
. Inoltre, il secondo e il terzo metodo verranno generati se aggiungi un valore chiave esistente. Non è una buona idea, stavi cercando TryAdd
? Infine, il secondo può essere sostituito conWhere(pair->!dic.ContainsKey(pair.Key)...
ToList()
non è una buona soluzione, quindi ho cambiato il codice. Puoi usare try { mainDic.AddRange(addDic); } catch { do something }
se non sei sicuro del terzo metodo. Il secondo metodo funziona perfettamente.
Nel caso in cui qualcuno si imbatta in questa domanda come me, è possibile ottenere "AddRange" utilizzando i metodi di estensione IEnumerable:
var combined =
dict1.Union(dict2)
.GroupBy(kvp => kvp.Key)
.Select(grp => grp.First())
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Il trucco principale quando si combinano i dizionari è gestire le chiavi duplicate. Nel codice sopra è la parte .Select(grp => grp.First())
. In questo caso si prende semplicemente il primo elemento dal gruppo di duplicati ma è possibile implementare una logica più sofisticata se necessario.
dict1
non si utilizza l'operatore di confronto di uguaglianza predefinito?
var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
La mia ipotesi è la mancanza di un output adeguato per l'utente su ciò che è accaduto. Dato che non puoi avere chiavi ripetute in un dizionario, come gestiresti l'unione di due dizionari dove alcune chiavi si intersecano? Certo potresti dire: "Non mi interessa" ma questo infrange la convenzione di restituire falso / lanciare un'eccezione per la ripetizione delle chiavi.
Add
, a parte questo può accadere più di una volta. Getterebbe lo stesso ArgumentException
che Add
fa, sicuramente?
NamedElementException
??), lanciata al posto o come innerException di un'ArgumentException, che specifica l'elemento denominato in conflitto ... alcune opzioni diverse, direi
Potresti farlo
Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);
public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
dic.Add(.., ..);
}
o usa un elenco per aggiungere intervallo e / o usando il modello sopra.
List<KeyValuePair<string, string>>
MethodThatReturnAnotherDic
. Viene dall'OP. Si prega di rivedere di nuovo la domanda e la mia risposta.
Se hai a che fare con un nuovo dizionario (e non hai righe esistenti da perdere), puoi sempre utilizzare ToDictionary () da un altro elenco di oggetti.
Quindi, nel tuo caso, faresti qualcosa del genere:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
Dictionary<string, string> dic = SomeList.ToDictionary...
Se sai che non avrai chiavi duplicate, puoi fare:
dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Verrà generata un'eccezione se è presente una coppia chiave / valore duplicata.
Non so perché questo non sia nel quadro; dovrebbe essere. Non c'è incertezza; lancia solo un'eccezione. Nel caso di questo codice, genera un'eccezione.
var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);
?
Sentiti libero di usare un metodo di estensione come questo:
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;
}
Ecco una soluzione alternativa usando c # 7 ValueTuples (tuple letterali)
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);
}
}
Usato come
segments
.Zip(values, (s, v) => (s.AsSpan().StartsWith("{") ? s.Trim('{', '}') : null, v))
.Where(zip => zip.Item1 != null)
.AddTo(queryParams);
Come altri hanno già detto, il motivo per cui Dictionary<TKey,TVal>.AddRange
non è implementato è perché ci sono vari modi in cui potresti voler gestire i casi in cui hai duplicati. Questo è anche il caso Collection
o interfacce come IDictionary<TKey,TVal>
, ICollection<T>
ecc
Lo List<T>
implementa solo e noterai che l' IList<T>
interfaccia non lo fa, per gli stessi motivi: il previsto comportamento quando si aggiunge un intervallo di valori a una raccolta può variare ampiamente, a seconda del contesto.
Il contesto della tua domanda suggerisce che non sei preoccupato per i duplicati, nel qual caso hai una semplice alternativa oneliner, usando Linq:
MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));