辞書にAddRangeがないのはなぜですか?


115

タイトルは十分に基本的ですが、なぜ私はできないのですか?

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


2
AddRangeがないものはたくさんあります。コレクションのように<>。List <>が持っているのはいつも奇妙に見えましたが、Collection <>や他のIListおよびICollectionオブジェクトはそうではありませんでした。
ティム

39
私はここでエリック・リッペルトを引き寄せます。「その機能を設計、指定、実装、テスト、文書化、出荷した人は誰もいないからです。」
Gabe Moothart、2011年

3
@ Gabe Moothart-それはまさに私が想定していたことです。他の人にそのラインを使うのが大好きです。彼らはそれを嫌います。:)
Tim

2
@GabeMoothartは、「できないから」、「できないから」、「さらには」という方が簡単ではないでしょうか。-私が言うのはそれほど楽しいものではないと思いますか?---(引用または言い換えたと思います)回答に対するフォローアップの質問は、「なぜ誰もその機能の設計、指定、実装、テスト、文書化、および出荷を行わなかったのはなぜですか?」です。 「誰もしなかったので」と応答することを余儀なくされました。これは、先に提案した応答と同等です。私が想像できる他の唯一のオプションは皮肉ではなく、OPが実際に疑問に思っていることです。
コードジョッキー

回答:


76

元の質問へのコメントはこれをかなりうまくまとめています:

誰もその機能を設計、指定、実装、テスト、文書化、出荷したことがないからです。-@Gabe Moothart

なぜですか?おそらく、辞書をマージする動作は、フレームワークのガイドラインに適合する方法で推論できないためです。

AddRangeデータの範囲は重複したエントリを許容するため、範囲は連想コンテナにとって意味がないため、存在しません。たとえば、IEnumerable<KeyValuePair<K,T>>そのコレクションがある場合、重複したエントリを防ぎません。

キーと値のペアのコレクションを追加したり、2つの辞書をマージしたりする動作は簡単です。ただし、複数の重複エントリを処理する方法の動作は異なります。

重複を処理するときのメソッドの動作は何ですか?

私が考えることができる少なくとも3つの解決策があります:

  1. 重複する最初のエントリの例外をスローします
  2. すべての重複エントリを含む例外をスローします
  3. 重複を無視する

例外がスローされた場合、元の辞書の状態はどうなりますか?

Addほとんどの場合、アトミック操作として実装されます。成功してコレクションの状態を更新するか、失敗してコレクションの状態は変更されません。AddRangeエラーを複製するため、失敗する可能性が、方法は一貫してその動作を維持するためにAddも、重複に例外をスローすることによって、それは、原子作る、と変わらないように、元の辞書の状態を残すことであろう。

APIコンシューマーとして、重複する要素を繰り返し削除するのは面倒です。これはAddRange、がすべての重複する値を含む単一の例外をスローする必要があることを意味します。

次に、選択は次のように要約されます。

  1. すべての重複を含む例外をスローし、元の辞書はそのままにします。
  2. 重複を無視して続行します。

両方のユースケースをサポートするための議論があります。そのためIgnoreDuplicatesに、署名にフラグを追加しますか?

IgnoreDuplicates基本となる実装が重複チェック用コードのバイパスと同じように(trueに設定)フラグはまた、有意な速度を提供するであろう。

これで、AddRange両方のケースをサポートできるようにするフラグがありますが、文書化されていない副作用があります(これは、Frameworkデザイナーが回避するために真剣に取り組んだものです)。

概要

重複の処理に関しては、明確で一貫した期待される動作がないため、それらをすべて一緒に処理せず、最初からメソッドを提供しない方が簡単です。

辞書をマージし続ける必要がある場合は、もちろん、独自の拡張メソッドを作成して辞書をマージすることができます。これは、アプリケーションで機能するように動作します。


37
完全に偽、ディクショナリにはAddRange(IEnumerable <KeyValuePair <K、T >> Values)が必要
Gusman

19
追加できる場合は、複数追加することもできます。重複したキーを持つアイテムを追加しようとしたときの動作は、単一の重複したキーを追加したときと同じです。
Uriah Blatherwick 2015

5
AddMultipleとは異なりますがAddRange、実装が不安定になることはありません。すべての重複するキーの配列で例外をスローしますか?または、遭遇した最初の重複キーで例外をスローしますか?例外がスローされた場合、辞書の状態はどうなりますか?そのまま、または成功したすべてのキー?
アラン

3
さて、今私は列挙型を手動で反復して個別に追加する必要がありますが、あなたが言及した重複についてのすべての警告があります。それをフレームワークから削除すると、どのように解決されますか?
doug65536

4
@ doug65536あなたは、APIの消費者として、今、あなたは、個々に何をしたいかを決めることができますのでAdd-それぞれをラップのいずれかAddtry...catch、重複道それをキャッチ。または、インデクサーを使用して、最初の値を新しい値で上書きします。または、を使用するContainsKey前に使用を事前に確認しAdd、元の値を保持します。フレームワークにAddRangeor AddMultipleメソッドが含まれている場合、発生したことを伝えるための唯一の簡単な方法は例外を介したものであり、関連する処理と回復も複雑ではありません。
Zev Spitz

36

私はいくつかの解決策を持っています:

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

楽しんで。


ToList()辞書は必要ありませんIEnumerable<KeyValuePair<TKey,TValue>。また、既存のキー値を追加すると、2番目と3番目のメソッドメソッドがスローされます。良い考えではありません、あなたは探していましたTryAddか?最後に、2つ目は次のように置き換えることができますWhere(pair->!dic.ContainsKey(pair.Key)...
Panagiotis Kanavos 2015年

2
OK、ToList()良い解決策ではないので、コードを変更しました。try { mainDic.AddRange(addDic); } catch { do something }3番目の方法がわからない場合に使用できます。2番目の方法は完全に機能します。
ADM-IT

こんにちはパナギオティス・カナボス、あなたが幸せであることを願っています。
ADM-IT

1
ありがとう、私はこれを盗んだ。
メタバディ

14

私のように誰かがこの質問に遭遇した場合-IEnumerable拡張メソッドを使用して「AddRange」を達成することは可能です:

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

辞書を組み合わせる際の主なトリックは、重複するキーを処理することです。上のコードでは、それが一部.Select(grp => grp.First())です。この場合、重複のグループから最初の要素を取得するだけですが、必要に応じて、より高度なロジックを実装できます。


デフォルトの等値比較子を使用dict1 しない場合どうなりますか?
mjwills 2017

LINQのメソッドは、関連がある場合、あなたがたIEqualityComparerに渡してみましょう:var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
カイル・マクレラン

12

何が起こったのかについてユーザーに適切な出力が不足していると思います。辞書では繰り返しキーを使用できないため、いくつかのキーが交差する2つの辞書をマージするにはどうすればよいでしょうか。もちろん、「気にしない」と言うこともできますが、これはfalseを返す、または繰り返しキーに対して例外をスローするという慣例を破っています。


5
これは、を呼び出すときにキークラッシュが発生する場所とはどのように異なるのですAddか。これは、同じスローしまうArgumentExceptionことAdd確実に、ありませんか?
nicodemus13

1
@ nicodemus13はい、ただし、どのキーが例外をスローしたかはわかりません。
ギャル

1
@Galは許可されますが、次のことができます:例外メッセージで競合するキーの名前を返す(彼らが何をしているのか知っている人に役立つと思います...)、またはそれを(の一部?)スローするArgumentExceptionへのparamName引数、またはOR-OR、ArgumentExceptionのNamedElementException代わりに、またはArgumentExceptionのinnerExceptionとして、新しい例外タイプを作成できます(競合する名前付き要素を指定します)。 ...いくつかの異なるオプション、私は言うでしょう
コードジョッキー

7

あなたはこれを行うことができます

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

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

または、addrangeや上記のパターンを使用するためにリストを使用します。

List<KeyValuePair<string, string>>

1
辞書には、別の辞書受け入れるコンストラクタが既にあります。
Panagiotis Kanavos 2015年

1
OPは辞書を複製するのではなく、追加範囲を望んでいます。私の例ではメソッドの名前はMethodThatReturnAnotherDic。それはOPから来ています。質問と私の回答をもう一度確認してください。
Valamas

1

新しいディクショナリを処理している場合(既存の行を失う必要がない場合)、別のオブジェクトリストからToDictionary()をいつでも使用できます。

したがって、あなたのケースでは、次のようなことをします:

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

5
新しい辞書を作成する必要はありません。書くだけですDictionary<string, string> dic = SomeList.ToDictionary...
Panagiotis Kanavos '

1

キーが重複しないことがわかっている場合は、次のようにできます。

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

キーと値のペアが重複している場合は、例外がスローされます。

これがフレームワークに含まれていない理由はわかりません。する必要があります。不確実性はありません。例外を投げるだけです。このコードの場合、例外をスローします。


元の辞書がを使用して作成された場合はどうなりvar caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);ますか?
mjwills 2017年

1

次のような拡張メソッドを自由に使用してください。

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

0

これは、c#7 ValueTuples(タプルリテラル)を使用した代替ソリューションです。

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

のように使用

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

0

他の人が述べたように、Dictionary<TKey,TVal>.AddRange実装されていない理由は、重複しているケースを処理する方法がいくつかあるためです。これはまた、の場合であるCollectionか、などのインターフェースIDictionary<TKey,TVal>ICollection<T>など

List<T>それを実装するだけIList<T>で、同じ理由でインターフェースが実装しないことに注意してください:期待される値の範囲をコレクションに追加するときに動作は、コンテキストによって大きく異なる可能性があります。

あなたの質問のコンテキストは、重複について心配していないことを示唆しています。

MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.