Warum hat Dictionary kein AddRange?

115

Der Titel ist einfach genug, warum kann ich nicht:

Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Sorgerecht
quelle
2
Es gibt viele, die AddRange nicht haben, was mich immer verwirrt hat. Wie Sammlung <>. Es schien nur immer seltsam, dass List <> es hatte, aber nicht Collection <> oder andere IList- und ICollection-Objekte.
Tim
39
Ich werde hier einen Eric Lippert ziehen: "Weil niemand diese Funktion jemals entworfen, spezifiziert, implementiert, getestet, dokumentiert und ausgeliefert hat."
Gabe Moothart
3
@ Gabe Moothart - genau das hatte ich angenommen. Ich liebe es, diese Linie bei anderen Menschen zu verwenden. Sie hassen es aber. :)
Tim
2
@GabeMoothart wäre es nicht einfacher zu sagen "Weil du nicht kannst" oder "Weil es nicht geht" oder sogar "Weil"? - Ich vermute, das macht nicht so viel Spaß oder so? --- Meine Folgefrage zu Ihrer Antwort (ich vermute, zitiert oder umschrieben) lautet: "Warum hat noch nie jemand diese Funktion entworfen, spezifiziert, implementiert, getestet, dokumentiert und ausgeliefert?", Zu der Sie möglicherweise gehören gezwungen, mit "Weil niemand es getan hat" zu antworten, was den Antworten entspricht, die ich zuvor vorgeschlagen habe. Die einzige andere Option, die ich mir vorstellen kann, ist nicht sarkastisch und das, was sich das OP tatsächlich gefragt hat.
Code Jockey

Antworten:

76

Ein Kommentar zur ursprünglichen Frage fasst dies ziemlich gut zusammen:

weil niemand diese Funktion jemals entworfen, spezifiziert, implementiert, getestet, dokumentiert und ausgeliefert hat. - @Gabe Moothart

Warum? Nun, wahrscheinlich, weil das Verhalten beim Zusammenführen von Wörterbüchern nicht in einer Weise begründet werden kann, die den Framework-Richtlinien entspricht.

AddRangeexistiert nicht, weil ein Bereich für einen assoziativen Container keine Bedeutung hat, da der Datenbereich doppelte Einträge zulässt. Wenn Sie beispielsweise eine haben IEnumerable<KeyValuePair<K,T>>, schützt diese Sammlung nicht vor doppelten Einträgen.

Das Hinzufügen einer Sammlung von Schlüssel-Wert-Paaren oder sogar das Zusammenführen von zwei Wörterbüchern ist unkompliziert. Das Verhalten beim Umgang mit mehreren doppelten Einträgen ist jedoch nicht so.

Wie sollte sich die Methode verhalten, wenn es sich um ein Duplikat handelt?

Ich kann mir mindestens drei Lösungen vorstellen:

  1. Wirf eine Ausnahme für den ersten Eintrag, der ein Duplikat ist
  2. eine Ausnahme auslösen, die alle doppelten Einträge enthält
  3. Duplikate ignorieren

Wie sollte der Status des ursprünglichen Wörterbuchs sein, wenn eine Ausnahme ausgelöst wird?

Addwird fast immer als atomare Operation implementiert: Sie ist erfolgreich und aktualisiert den Status der Sammlung, oder sie schlägt fehl, und der Status der Sammlung bleibt unverändert. Da AddRangedies aufgrund doppelter Fehler fehlschlagen kann, besteht die Möglichkeit, das Verhalten konsistent zu halten Add, darin, es atomar zu machen, indem eine Ausnahme für ein Duplikat ausgelöst wird, und den Status des ursprünglichen Wörterbuchs unverändert zu lassen.

Als API-Konsument wäre es mühsam, doppelte Elemente iterativ entfernen zu müssen, was bedeutet, dass AddRangeeine einzige Ausnahme ausgelöst werden sollte, die alle doppelten Werte enthält.

Die Wahl läuft dann auf Folgendes hinaus:

  1. Lösen Sie eine Ausnahme mit allen Duplikaten aus und lassen Sie das ursprüngliche Wörterbuch in Ruhe.
  2. Duplikate ignorieren und fortfahren.

Es gibt Argumente für die Unterstützung beider Anwendungsfälle. Fügen Sie IgnoreDuplicatesdazu der Signatur ein Flag hinzu?

Das IgnoreDuplicatesFlag (wenn es auf true gesetzt ist) würde ebenfalls eine erhebliche Beschleunigung bewirken, da die zugrunde liegende Implementierung den Code für die doppelte Überprüfung umgehen würde.

Jetzt haben Sie ein Flag, mit dem AddRangebeide Fälle unterstützt werden können, das jedoch einen undokumentierten Nebeneffekt hat (was die Framework-Designer sehr hart vermieden haben).

Zusammenfassung

Da es beim Umgang mit Duplikaten kein klares, konsistentes und erwartetes Verhalten gibt, ist es einfacher, nicht alle zusammen zu behandeln und zunächst nicht die Methode bereitzustellen.

Wenn Sie ständig Wörterbücher zusammenführen müssen, können Sie natürlich Ihre eigene Erweiterungsmethode zum Zusammenführen von Wörterbüchern schreiben, die sich so verhält, wie es für Ihre Anwendung (en) funktioniert.

Alan
quelle
37
Völlig falsch, ein Wörterbuch sollte einen AddRange (IEnumerable <KeyValuePair <K, T >>
Gusman
19
Wenn es Hinzufügen haben kann, sollte es auch mehrere hinzufügen können. Das Verhalten beim Hinzufügen von Elementen mit doppelten Schlüsseln sollte das gleiche sein wie beim Hinzufügen einzelner doppelter Schlüssel.
Uriah Blatherwick
5
AddMultipleist anders als AddRange, unabhängig davon, ob die Implementierung wackelig sein wird: Wirfst du eine Ausnahme mit einem Array aller doppelten Schlüssel? Oder werfen Sie eine Ausnahme auf den ersten doppelten Schlüssel, auf den Sie stoßen? Wie sollte der Status des Wörterbuchs sein, wenn eine Ausnahme ausgelöst wird? Makellos oder alle Schlüssel, die erfolgreich waren?
Alan
3
Okay, jetzt muss ich meine Aufzählung manuell iterieren und einzeln hinzufügen, mit allen Vorbehalten bezüglich der von Ihnen erwähnten Duplikate. Wie löst das Weglassen aus dem Framework etwas?
Doug65536
4
@ doug65536 Da Sie als API-Konsument jetzt entscheiden können, was Sie mit jeder Person tun möchten Add- wickeln Sie entweder jede Addin a ein try...catchund fangen Sie die Duplikate auf diese Weise ab. oder verwenden Sie den Indexer und überschreiben Sie den ersten Wert mit dem späteren Wert; oder überprüfen Sie die Verwendung vorab, ContainsKeybevor Sie dies versuchen Add, und behalten Sie so den ursprünglichen Wert bei. Wenn das Framework eine AddRangeoder AddMultiple-Methode hätte, wäre die einzige einfache Möglichkeit, das Geschehene zu kommunizieren, eine Ausnahme, und die damit verbundene Handhabung und Wiederherstellung wäre nicht weniger kompliziert.
Zev Spitz
36

Ich habe eine Lösung:

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

Habe Spaß.

EINGESTEHEN
quelle
Sie brauchen nicht ToList(), ein Wörterbuch ist ein IEnumerable<KeyValuePair<TKey,TValue>. Die zweite und dritte Methodenmethode werden auch ausgelöst, wenn Sie einen vorhandenen Schlüsselwert hinzufügen. Keine gute Idee, suchten Sie TryAdd? Schließlich kann die zweite durchWhere(pair->!dic.ContainsKey(pair.Key)...
Panagiotis Kanavos
2
Ok, ToList()ist keine gute Lösung, also habe ich den Code geändert. Sie können verwenden, try { mainDic.AddRange(addDic); } catch { do something }wenn Sie sich für die dritte Methode nicht sicher sind. Die zweite Methode funktioniert perfekt.
ADM-IT
Hallo Panagiotis Kanavos, ich hoffe du bist glücklich.
ADM-IT
1
Danke, ich habe das gestohlen.
Metabuddy
14

Falls jemand wie ich auf diese Frage stößt, ist es möglich, "AddRange" mithilfe von IEnumerable-Erweiterungsmethoden zu erreichen:

var combined =
    dict1.Union(dict2)
        .GroupBy(kvp => kvp.Key)
        .Select(grp => grp.First())
        .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

Der Haupttrick beim Kombinieren von Wörterbüchern ist der Umgang mit den doppelten Schlüsseln. Im obigen Code ist es der Teil .Select(grp => grp.First()). In diesem Fall wird einfach das erste Element aus der Gruppe der Duplikate verwendet, aber Sie können dort bei Bedarf eine komplexere Logik implementieren.

Rafal Zajac
quelle
Was ist, wenn der Standardgleichheitsvergleich dict1 nicht verwendet wird?
mjwills
Mit Linq-Methoden können Sie IEqualityComparer bei Bedarf übergeben:var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
Kyle McClellan
12

Ich vermute, dass dem Benutzer keine ordnungsgemäße Ausgabe darüber vorliegt, was passiert ist. Wie würden Sie mit dem Zusammenführen von zwei Wörterbüchern umgehen, bei denen sich einige Schlüssel überschneiden, da sich Schlüssel in einem Wörterbuch nicht wiederholen können? Sicher könnte man sagen: "Es ist mir egal", aber das verstößt gegen die Konvention, falsch zurückzugeben / eine Ausnahme für das Wiederholen von Schlüsseln auszulösen.

Gal
quelle
5
Wie unterscheidet sich das von dem Ort, an dem Sie beim Anrufen einen Tastenkonflikt haben? AddAbgesehen davon kann dies mehr als einmal vorkommen. Es würde die gleiche werfen ArgumentExceptiondas der AddFall ist, oder ?
nicodemus13
1
@ nicodemus13 Ja, aber Sie würden nicht wissen, welcher Schlüssel die Ausnahme ausgelöst hat, nur dieser EINIGE Schlüssel war eine Wiederholung.
Gal
1
@Gal gewährt, aber Sie könnten: den Namen des widersprüchlichen Schlüssels in der Ausnahmemeldung zurückgeben (nützlich für jemanden, der weiß, was er tut, nehme ich an ...), ODER Sie könnten ihn als (Teil von?) Der setzen paramName-Argument für die von Ihnen ausgelöste ArgumentException, ODER-ODER, Sie können einen neuen Ausnahmetyp erstellen (möglicherweise ist eine ausreichend generische Option NamedElementException??), der entweder anstelle oder als innerException einer ArgumentException ausgelöst wird und das benannte Element angibt, das in Konflikt steht ... ein paar verschiedene Optionen, würde ich sagen
Code Jockey
7

Du könntest das tun

Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);

public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
    dic.Add(.., ..);
}

oder verwenden Sie eine Liste für die Adressierung und / oder verwenden Sie das obige Muster.

List<KeyValuePair<string, string>>
Valamas
quelle
1
Das Wörterbuch verfügt bereits über einen Konstruktor, der ein anderes Wörterbuch akzeptiert .
Panagiotis Kanavos
1
OP möchte einen Bereich hinzufügen und kein Wörterbuch klonen. Wie für den Namen der Methode in meinem Beispiel MethodThatReturnAnotherDic. Es kommt aus dem OP. Bitte überprüfen Sie die Frage und meine Antwort erneut.
Valamas
1

Wenn Sie mit einem neuen Wörterbuch arbeiten (und keine vorhandenen Zeilen zu verlieren haben), können Sie ToDictionary () immer aus einer anderen Liste von Objekten verwenden.

In Ihrem Fall würden Sie also Folgendes tun:

Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
WEFX
quelle
5
Sie müssen nicht einmal ein neues Wörterbuch erstellen, schreiben Sie einfachDictionary<string, string> dic = SomeList.ToDictionary...
Panagiotis Kanavos
1

Wenn Sie wissen, dass Sie keine doppelten Schlüssel haben werden, können Sie Folgendes tun:

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

Es wird eine Ausnahme ausgelöst, wenn ein doppeltes Schlüssel / Wert-Paar vorhanden ist.

Ich weiß nicht, warum dies nicht im Rahmen ist; sollte sein. Es gibt keine Unsicherheit; Wirf einfach eine Ausnahme. Bei diesem Code wird eine Ausnahme ausgelöst.

toddmo
quelle
Was ist, wenn das ursprüngliche Wörterbuch mit erstellt wurde var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);?
mjwills
1

Fühlen Sie sich frei, Erweiterungsmethode wie folgt zu verwenden:

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;
}
Franziee
quelle
0

Hier ist eine alternative Lösung mit c # 7 ValueTuples (Tupelliterale)

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);
    }
}

Gebraucht wie

segments
    .Zip(values, (s, v) => (s.AsSpan().StartsWith("{") ? s.Trim('{', '}') : null, v))
    .Where(zip => zip.Item1 != null)
    .AddTo(queryParams);
Anders
quelle
0

Wie bereits erwähnt, liegt der Grund für die Nichtimplementierung darin, dass Dictionary<TKey,TVal>.AddRangeSie auf verschiedene Weise mit Fällen umgehen können, in denen Duplikate vorhanden sind. Dies ist auch der Fall für Collectionoder Schnittstellen wie IDictionary<TKey,TVal>, ICollection<T>usw.

Nur List<T>implementiert, und Sie werden feststellen , dass die IList<T>Schnittstelle nicht, aus den gleichen Gründen: das erwartete Verhalten , wenn eine Reihe von Werten zu einer Sammlung hinzugefügt breit variieren kann, je nach Kontext.

Der Kontext Ihrer Frage legt nahe, dass Sie sich keine Sorgen um Duplikate machen. In diesem Fall haben Sie eine einfache Oneliner-Alternative mit Linq:

MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));
Ama
quelle