web-dev-qa-db-fra.com

Pourquoi Dictionnaire n'a pas AddRange?

Le titre est assez basique, pourquoi ne puis-je pas:

Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
103
Custodio

Un commentaire à la question initiale résume assez bien la situation:

parce que personne n'a jamais conçu, spécifié, mis en œuvre, testé, documenté et livré cette fonctionnalité. - @Gabe Moothart

Quant à pourquoi? Probablement parce que le comportement de la fusion de dictionnaires ne peut pas être expliqué d'une manière qui soit conforme aux directives du Framework.

AddRange n'existe pas, car une plage n'a pas de signification pour un conteneur associatif, car la plage de données permet des entrées en double. Si vous aviez un IEnumerable<KeyValuePair<K,T>> cette collection ne protège pas contre les entrées en double.

L'ajout d'une collection de paires clé-valeur, voire la fusion de deux dictionnaires, est un comportement simple. Le comportement de la gestion de plusieurs entrées en double ne l’est toutefois pas.

Quel devrait être le comportement de la méthode quand il s'agit d'un doublon?

Il y a au moins trois solutions auxquelles je peux penser:

  1. jette une exception pour l'entrée en premier qui est un doublon
  2. lancer une exception qui contient toutes les entrées en double
  3. Ignorer les doublons

Quand une exception est levée, quel devrait être l'état du dictionnaire d'origine?

Add est presque toujours implémenté en tant qu'opération atomique: il réussit et met à jour l'état de la collection ou échoue, et l'état de la collection reste inchangé. Comme AddRange peut échouer à cause d’erreurs de duplication, la façon de garder son comportement cohérent avec Add consiste également à le rendre atomique en lançant une exception sur tout doublon et en laissant l’état de l’original. dictionnaire 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 lève une seule exception qui contient tout les valeurs en double.

Le choix se résume alors à:

  1. Lance une exception avec tous les doublons, laissant le dictionnaire original seul.
  2. Ignorer les doublons et continuer.

Il existe des arguments pour prendre en charge les deux cas d'utilisation. Pour ce faire, ajoutez-vous un indicateur IgnoreDuplicates à la signature?

Le drapeau IgnoreDuplicates (lorsqu'il est défini sur true) fournirait également une vitesse importante, car l’implémentation sous-jacente contournerait le code pour la vérification des doublons.

Alors maintenant, vous avez un drapeau qui permet à AddRange de prendre en charge les deux cas, mais qui a un effet secondaire non documenté (ce que les concepteurs de Framework ont ​​travaillé très dur pour éviter).

Résumé

En l'absence de comportement clair, cohérent et attendu en ce qui concerne 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 vous retrouvez continuellement à fusionner des dictionnaires, vous pouvez bien sûr écrire votre propre méthode d’extension pour fusionner des dictionnaires, laquelle se comportera de manière à fonctionner de manière appropriée pour vos applications.

64
Alan

J'ai trouvé une autre 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 void AddRangeOverride<TKey, TValue>(this Dictionary<TKey, TValue> dic, Dictionary<TKey, TValue> dicToAdd)
    {
        dicToAdd.ForEach(x => dic[x.Key] = x.Value);
    }

    public static void AddRangeNewOnly<TKey, TValue>(this Dictionary<TKey, TValue> dic, Dictionary<TKey, TValue> dicToAdd)
    {
        dicToAdd.ForEach(x => { if (!dic.ContainsKey(x.Key)) dic.Add(x.Key, x.Value); });
    }

    public static void AddRange<TKey, TValue>(this Dictionary<TKey, TValue> dic, Dictionary<TKey, TValue> dicToAdd)
    {
        dicToAdd.ForEach(x => dic.Add(x.Key, x.Value));
    }

    public static bool ContainsKeys<TKey, TValue>(this Dictionary<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.

27
ADM-IT

Mon hypothèse est le manque de sortie appropriée pour l'utilisateur sur 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 lorsque certaines clés se croisent? Bien sûr, vous pouvez dire: "Je m'en fiche", mais cela rompt avec la convention qui consiste à renvoyer une erreur/une exception en cas de répétition de clés.

12
Gal

Au cas où quelqu'un rencontrerait cette question comme moi, il est possible d'obtenir "AddRange" en utilisant des 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 dupliquées. 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.

10
Rafal Zajac

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 addrange et/ou en utilisant le modèle ci-dessus.

List<KeyValuePair<string, string>>
6
Valamas

Si vous savez que vous n'allez pas avoir des clés en double, vous pouvez faire:

dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

Il lève une exception s'il existe 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. il suffit de lancer une exception. Dans le cas de ce code, il lève une exception.

1
toddmo

N'hésitez pas à utiliser la méthode d'extension comme ceci:

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;
}
1
Franziee

Si vous utilisez 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 ça:

Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
1
WEFX

Voici une solution alternative utilisant c # 7 ValueTuples (Tuple literals)

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);
0
Anders