Le titre est assez basique, pourquoi ne puis-je pas:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Le titre est assez basique, pourquoi ne puis-je pas:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Réponses:
Un commentaire à la question originale résume assez bien cela:
car personne n'a jamais conçu, spécifié, implémenté, testé, documenté et expédié cette fonctionnalité. - @Gabe Moothart
Quant à savoir pourquoi? Eh bien, probablement parce que le comportement de la fusion de dictionnaires ne peut pas être raisonné d'une manière qui correspond aux directives du Framework.
AddRange
n'existe pas car une plage n'a aucune signification pour un conteneur associatif, car la plage de données permet des entrées en double. Par exemple, si vous en aviez une, IEnumerable<KeyValuePair<K,T>>
cette collection ne vous protège pas contre les entrées en double.
Le comportement consistant à ajouter une collection de paires clé-valeur, ou même à fusionner deux dictionnaires est simple. Le comportement de la façon de traiter plusieurs entrées en double, cependant, ne l'est pas.
Quel doit être le comportement de la méthode lorsqu'elle traite un doublon?
Il y a au moins trois solutions auxquelles je peux penser:
Lorsqu'une exception est levée, quel devrait être l'état du dictionnaire d'origine?
Add
est presque toujours implémentée comme une opération atomique: elle réussit et met à jour l'état de la collection, ou elle échoue, et l'état de la collection reste inchangé. Comme cela AddRange
peut échouer en raison d'erreurs dupliquées, la façon de garder son comportement cohérent Add
serait de le rendre également atomique en lançant une exception sur tout doublon, et de laisser l'état du dictionnaire d'origine inchangé.
En tant que consommateur d'API, il serait fastidieux de devoir supprimer de manière itérative les éléments en double, ce qui implique que le AddRange
devrait lever une seule exception contenant toutes les valeurs en double.
Le choix se résume alors à:
Il existe des arguments pour prendre en charge les deux cas d'utilisation. Pour ce faire, ajoutez-vous un IgnoreDuplicates
drapeau à la signature?
L' IgnoreDuplicates
indicateur (lorsqu'il est défini sur true) fournirait également une accélération significative, car l'implémentation sous-jacente contournerait le code pour la vérification des doublons.
Alors maintenant, vous avez un indicateur qui permet à la AddRange
de prendre en charge les deux cas, mais a un effet secondaire non documenté (ce que les concepteurs du Framework ont travaillé très dur pour éviter).
Résumé
Comme il n'y a pas de comportement clair, cohérent et attendu lorsqu'il s'agit de traiter les doublons, il est plus facile de ne pas les traiter tous ensemble et de ne pas fournir la méthode pour commencer.
Si vous devez continuellement fusionner des dictionnaires, vous pouvez bien sûr écrire votre propre méthode d'extension pour fusionner des dictionnaires, qui se comportera d'une manière qui fonctionne pour vos applications.
AddMultiple
est différent de AddRange
, quelle que soit sa mise en œuvre va être bancale: lancez-vous une exception avec un tableau de toutes les clés en double? Ou lancez-vous une exception sur la première clé en double que vous rencontrez? Quel devrait être l'état du dictionnaire si une exception est levée? Pristine, ou toutes les clés qui ont réussi?
Add
- soit envelopper chacun Add
dans un try...catch
et attraper les doublons de cette façon; ou utilisez l'indexeur et remplacez la première valeur par la dernière valeur; ou vérifiez de manière préventive en utilisant ContainsKey
avant d'essayer Add
, préservant ainsi la valeur d'origine. Si le framework avait une méthode AddRange
ou AddMultiple
, le seul moyen simple de communiquer ce qui s'était passé serait via une exception, et la gestion et la récupération impliquées ne seraient pas moins compliquées.
J'ai une solution:
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;
}
}
}
}
S'amuser.
ToList()
, un dictionnaire est un fichier IEnumerable<KeyValuePair<TKey,TValue>
. En outre, les deuxième et troisième méthodes de méthode seront lancées si vous ajoutez une valeur de clé existante. Pas une bonne idée, tu cherchais TryAdd
? Enfin, le deuxième peut être remplacé parWhere(pair->!dic.ContainsKey(pair.Key)...
ToList()
n'est pas une bonne solution alors j'ai changé le code. Vous pouvez utiliser try { mainDic.AddRange(addDic); } catch { do something }
si vous n'êtes pas sûr de la troisième méthode. La deuxième méthode fonctionne parfaitement.
Dans le cas où quelqu'un rencontre cette question comme moi, il est possible d'atteindre "AddRange" en utilisant les méthodes d'extension IEnumerable:
var combined =
dict1.Union(dict2)
.GroupBy(kvp => kvp.Key)
.Select(grp => grp.First())
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
L'astuce principale lors de la combinaison de dictionnaires consiste à gérer les clés en double. Dans le code ci-dessus, c'est la partie .Select(grp => grp.First())
. Dans ce cas, il prend simplement le premier élément du groupe de doublons mais vous pouvez y implémenter une logique plus sophistiquée si nécessaire.
dict1
n'utilise pas le comparateur d'égalité par défaut?
var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
Ma conjecture est le manque de sortie appropriée à l'utilisateur quant à ce qui s'est passé. Comme vous ne pouvez pas avoir de clés répétitives dans un dictionnaire, comment géreriez-vous la fusion de deux dictionnaires où certaines touches se croisent? Bien sûr, vous pouvez dire: "Je m'en fous", mais cela enfreint la convention consistant à renvoyer false / à lancer une exception pour la répétition des clés.
Add
, à part cela, cela peut se produire plus d'une fois. Il jetterait le même ArgumentException
qui Add
fait, sûrement?
NamedElementException
??), soit levée à la place de soit en tant que innerException d'une ArgumentException, qui spécifie l'élément nommé qui est en conflit ... quelques options différentes, je dirais
Tu pourrais faire ça
Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);
public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
dic.Add(.., ..);
}
ou utilisez une liste pour ajouter une plage et / ou utiliser le modèle ci-dessus.
List<KeyValuePair<string, string>>
MethodThatReturnAnotherDic
. Cela vient de l'OP. Veuillez revoir la question et ma réponse.
Si vous traitez avec un nouveau dictionnaire (et que vous n'avez pas de lignes existantes à perdre), vous pouvez toujours utiliser ToDictionary () à partir d'une autre liste d'objets.
Donc, dans votre cas, vous feriez quelque chose comme ceci:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
Dictionary<string, string> dic = SomeList.ToDictionary...
Si vous savez que vous n'allez pas avoir de clés en double, vous pouvez faire:
dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Il lèvera une exception s'il y a une paire clé / valeur en double.
Je ne sais pas pourquoi ce n'est pas dans le cadre; devrait être. Il n'y a pas d'incertitude; jetez simplement une exception. Dans le cas de ce code, il lève une exception.
var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);
?
N'hésitez pas à utiliser une méthode d'extension comme celle-ci:
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;
}
Voici une solution alternative utilisant c # 7 ValueTuples (littéraux de tuple)
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);
}
}
Utilisé comme
segments
.Zip(values, (s, v) => (s.AsSpan().StartsWith("{") ? s.Trim('{', '}') : null, v))
.Where(zip => zip.Item1 != null)
.AddTo(queryParams);
Comme d'autres l'ont mentionné, la raison pour laquelle Dictionary<TKey,TVal>.AddRange
n'est pas implémenté est qu'il existe plusieurs façons de gérer les cas où vous avez des doublons. Ceci est également le cas pour Collection
ou interfaces telles que IDictionary<TKey,TVal>
, ICollection<T>
, etc.
Seulement List<T>
met en œuvre, et vous remarquerez que l' IList<T>
interface ne pas, pour les mêmes raisons: l' attendu comportement lors de l'ajout d'une plage de valeurs à une collection peut varier largement, selon le contexte.
Le contexte de votre question suggère que vous ne vous inquiétez pas des doublons, auquel cas vous avez une simple alternative oneliner, en utilisant Linq:
MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));