web-dev-qa-db-ja.com

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

タイトルは十分に基本的です、なぜ私はできません:

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

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

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

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

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

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

重複を処理する場合のメソッドの動作はどうなりますか?

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

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

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

Addは、ほとんど常にアトミック操作として実装されます。成功してコレクションの状態を更新するか、失敗するか、コレクションの状態は変更されません。 AddRangeは重複エラーが原因で失敗する可能性があるため、その動作をAddと一貫性を保つ方法は、重複に対して例外をスローすることでアトミックにし、元の状態のままにすることです。変更されていない辞書。

APIコンシューマーとして、重複する要素を繰り返し削除しなければならないのは退屈です。つまり、AddRangeallの重複値を含む単一の例外をスローすることを意味します。

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

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

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

IgnoreDuplicatesフラグ(trueに設定されている場合)も大幅にスピードアップします。これは、基になる実装が重複チェックのためにコードをバイパスするためです。

これで、AddRangeが両方のケースをサポートできるフラグがありますが、文書化されていない副作用があります(フレームワークの設計者が回避するために本当に努力したことです)。

概要

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

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

64
Alan

私はanの解決策を見つけました:

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

}

楽しんで。

27
ADM-IT

私の推測では、何が起こったのかに関するユーザーへの適切な出力が不足しています。辞書にキーを繰り返すことはできないので、いくつかのキーが交差する2つの辞書のマージをどのように処理しますか?確かに「私は気にしない」と言うことができますが、それはfalseを返す/繰り返しキーの例外をスローするという慣習に違反しています。

12
Gal

私のようなこの質問に誰かが出くわした場合-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())部分です。この場合、重複グループから最初の要素を取得するだけですが、必要に応じて、より高度なロジックを実装できます。

10
Rafal Zajac

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

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>>
6
Valamas

キーが重複しないことがわかっている場合は、次の操作を実行できます。

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

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

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

1
toddmo

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

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

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

だから、あなたの場合、あなたはこのようなことをするでしょう:

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

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
Anders