Mengapa Kamus tidak memiliki AddRange?

115

Judul cukup mendasar, mengapa saya tidak bisa:

Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Custodio
sumber
2
Ada banyak hal yang tidak memiliki AddRange, yang selalu membuat saya bingung. Seperti Koleksi <>. Selalu tampak aneh bahwa List <> memilikinya, tetapi tidak Collection <> atau objek IList dan ICollection lainnya.
Tim
39
Saya akan menarik Eric Lippert di sini: "karena tidak ada yang pernah merancang, menentukan, menerapkan, menguji, mendokumentasikan, dan mengirimkan fitur itu."
Gabe Moothart
3
@ Gabe Moothart - itulah yang saya asumsikan. Saya suka menggunakan kalimat itu pada orang lain. Mereka membencinya. :)
Tim
2
@GabeMoothart bukankah lebih mudah untuk mengatakan "Karena Anda tidak bisa" atau "Karena tidak bisa" atau bahkan "Karena"? - Kurasa itu tidak menyenangkan untuk dikatakan atau semacamnya? --- Pertanyaan tindak lanjut saya untuk tanggapan Anda (saya menduga dikutip atau diparafrasekan) adalah "Mengapa tidak ada orang yang pernah merancang, menentukan, menerapkan, menguji, mendokumentasikan, dan mengirimkan fitur itu?", Yang mungkin sangat Anda sukai terpaksa menjawab dengan "Karena tidak ada yang melakukannya", yang setara dengan tanggapan yang saya sarankan sebelumnya. Satu-satunya pilihan lain yang dapat saya bayangkan bukanlah sarkastik dan itulah yang sebenarnya ditanyakan oleh OP.
Kode Jockey

Jawaban:

76

Komentar untuk pertanyaan awal merangkum hal ini dengan cukup baik:

karena tidak ada yang pernah merancang, menentukan, menerapkan, menguji, mendokumentasikan, dan mengirimkan fitur itu. - @Gabe Moothart

Mengapa demikian? Yah, mungkin karena perilaku penggabungan kamus tidak dapat dipikirkan dengan cara yang sesuai dengan pedoman Kerangka.

AddRangetidak ada karena rentang tidak memiliki arti apa pun bagi wadah asosiatif, karena rentang data memungkinkan entri duplikat. Misalnya jika Anda memiliki IEnumerable<KeyValuePair<K,T>>koleksi yang tidak melindungi dari entri duplikat.

Perilaku menambahkan kumpulan pasangan nilai kunci, atau bahkan menggabungkan dua kamus sangatlah mudah. Perilaku bagaimana menangani beberapa entri duplikat, bagaimanapun, tidak.

Apa yang harus menjadi perilaku metode ketika berurusan dengan duplikat?

Setidaknya ada tiga solusi yang dapat saya pikirkan:

  1. melempar pengecualian untuk entri pertama yang merupakan duplikat
  2. melempar pengecualian yang berisi semua entri duplikat
  3. Abaikan duplikat

Ketika pengecualian dilempar, bagaimana seharusnya keadaan kamus aslinya?

Addhampir selalu diimplementasikan sebagai operasi atom: berhasil dan memperbarui status koleksi, atau gagal, dan status koleksi tidak berubah. Seperti AddRangebisa gagal karena kesalahan duplikat, cara untuk menjaga perilakunya konsisten dengan Addjuga membuatnya atomic dengan melemparkan pengecualian pada duplikat apa pun, dan membiarkan status kamus asli tidak berubah.

Sebagai konsumen API, akan membosankan jika harus menghapus elemen duplikat secara berulang, yang menyiratkan bahwa AddRangeharus melontarkan satu pengecualian yang berisi semua nilai duplikat.

Pilihannya kemudian bermuara pada:

  1. Lemparkan pengecualian dengan semua duplikat, biarkan kamus aslinya.
  2. Abaikan duplikat dan lanjutkan.

Ada argumen untuk mendukung kedua kasus penggunaan tersebut. Untuk melakukan itu, apakah Anda menambahkan sebuah IgnoreDuplicatesbendera ke tanda tangan?

The IgnoreDuplicatesflag (ketika diatur ke true) juga akan memberikan kecepatan yang signifikan atas, sebagai implementasi yang mendasari akan bypass kode untuk duplikat memeriksa.

Jadi sekarang, Anda memiliki bendera yang memungkinkan AddRangeuntuk mendukung kedua kasus, tetapi memiliki efek samping yang tidak terdokumentasi (yang merupakan sesuatu yang desainer Framework bekerja sangat keras untuk dihindari).

Ringkasan

Karena tidak ada perilaku yang jelas, konsisten, dan diharapkan saat berurusan dengan duplikat, lebih mudah untuk tidak menangani semuanya bersama-sama, dan tidak menyediakan metode untuk memulai.

Jika Anda mendapati diri Anda terus-menerus harus menggabungkan kamus, Anda tentu saja dapat menulis metode ekstensi Anda sendiri untuk menggabungkan kamus, yang akan berperilaku sesuai dengan aplikasi Anda.

Alan
sumber
37
Benar-benar salah, kamus harus memiliki AddRange (IEnumerable <KeyValuePair <K, T >> Values)
Gusman
19
Jika dapat memiliki Tambah, itu juga harus dapat menambahkan beberapa. Perilaku saat Anda mencoba menambahkan item dengan kunci duplikat harus sama seperti saat Anda menambahkan satu kunci duplikat.
Uriah Blatherwick
5
AddMultipleberbeda dari AddRange, terlepas dari implementasinya akan menjadi miring: Apakah Anda membuang pengecualian dengan array dari semua kunci duplikat? Atau apakah Anda memberikan pengecualian pada kunci duplikat pertama yang Anda temui? Bagaimana seharusnya keadaan kamus jika pengecualian dilempar? Murni, atau semua kunci yang berhasil?
Alan
3
Oke, jadi sekarang saya harus mengulang secara manual enumerable saya dan menambahkannya satu per satu, dengan semua peringatan tentang duplikat yang Anda sebutkan. Bagaimana menghilangkannya dari kerangka kerja menyelesaikan sesuatu?
doug65536
4
@ doug65536 Karena Anda, sebagai konsumen API, sekarang dapat memutuskan apa yang ingin Anda lakukan dengan setiap individu Add- baik bungkus masing-masing Adddalam a try...catchdan tangkap duplikatnya dengan cara itu; atau gunakan pengindeks dan timpa nilai pertama dengan nilai selanjutnya; atau periksa terlebih dahulu menggunakan ContainsKeysebelum mencoba Add, dengan demikian mempertahankan nilai aslinya. Jika kerangka kerja memiliki metode AddRangeatau AddMultiple, satu-satunya cara sederhana untuk mengkomunikasikan apa yang telah terjadi adalah melalui pengecualian, dan penanganan serta pemulihan yang terlibat tidak kalah rumitnya.
Zev Spitz
36

Saya punya beberapa solusi:

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

Selamat bersenang-senang.

MENGAKUI
sumber
Anda tidak perlu ToList(), kamus adalah IEnumerable<KeyValuePair<TKey,TValue>. Selain itu, metode kedua dan ketiga akan dibuang jika Anda menambahkan nilai kunci yang ada. Bukan ide yang bagus, apa yang Anda cari TryAdd? Akhirnya, yang kedua bisa diganti denganWhere(pair->!dic.ContainsKey(pair.Key)...
Panagiotis Kanavos
2
Ok, ToList()ini bukan solusi yang baik jadi saya telah mengubah kodenya. Anda dapat menggunakan try { mainDic.AddRange(addDic); } catch { do something }jika Anda tidak yakin untuk metode ketiga. Metode kedua bekerja dengan sempurna.
ADM-IT
Hai Panagiotis Kanavos, saya harap Anda bahagia.
ADM-IT
1
Terima kasih, saya mencuri ini.
metabuddy
14

Jika seseorang menemukan pertanyaan ini seperti saya - itu mungkin untuk mencapai "AddRange" dengan menggunakan metode ekstensi IEnumerable:

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

Trik utama saat menggabungkan kamus adalah berurusan dengan kunci duplikat. Dalam kode di atas itu adalah bagian .Select(grp => grp.First()). Dalam hal ini, ini hanya mengambil elemen pertama dari grup duplikat tetapi Anda dapat menerapkan logika yang lebih canggih di sana jika diperlukan.

Rafal Zajac
sumber
Bagaimana jika dict1 tidak menggunakan pembanding kesetaraan default?
mjwills
Metode Linq memungkinkan Anda meneruskan IEqualityComparer jika relevan:var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
Kyle McClellan
12

Dugaan saya adalah kurangnya output yang tepat bagi pengguna tentang apa yang terjadi. Karena Anda tidak dapat memiliki kunci berulang dalam kamus, bagaimana Anda menangani penggabungan dua kamus di mana beberapa kunci berpotongan? Tentu Anda bisa mengatakan: "Saya tidak peduli" tapi itu melanggar konvensi mengembalikan false / melempar pengecualian untuk kunci berulang.

Gal
sumber
5
Apa bedanya dengan saat Anda mengalami bentrokan kunci saat menelepon Add, selain itu hal itu bisa terjadi lebih dari sekali. Itu akan melempar sama ArgumentExceptionseperti Additu, kan?
nicodemus13
1
@ nicodemus13 Ya, tetapi Anda tidak akan tahu kunci mana yang mengeluarkan Pengecualian, hanya kunci BEBERAPA yang diulang.
Gal
1
@Gal diberikan, tetapi Anda dapat: mengembalikan nama kunci yang bentrok dalam pesan pengecualian (berguna bagi seseorang yang tahu apa yang mereka lakukan, saya kira ...), ATAU Anda dapat menaruhnya sebagai (bagian dari?) paramName argumen ke ArgumentException yang Anda lempar, OR-OR, Anda bisa membuat tipe pengecualian baru (mungkin opsi yang cukup umum mungkin NamedElementException??), baik dilemparkan sebagai ganti atau sebagai innerException dari ArgumentException, yang menentukan elemen bernama yang konflik ... beberapa opsi berbeda, menurut saya
Code Jockey
7

Kamu bisa melakukan ini

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

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

atau gunakan List untuk addrange dan / atau menggunakan pola di atas.

List<KeyValuePair<string, string>>
Valamas
sumber
1
Kamus sudah memiliki konstruktor yang menerima kamus lain .
Panagiotis Kanavos
1
OP ingin menambah, bukan mengkloning kamus. Adapun nama metode dalam contoh saya MethodThatReturnAnotherDic. Itu berasal dari OP. Harap tinjau kembali pertanyaan dan jawaban saya.
Valamas
1

Jika Anda berurusan dengan Dictionary baru (dan Anda tidak memiliki baris yang hilang), Anda selalu dapat menggunakan ToDictionary () dari daftar objek lain.

Jadi, dalam kasus Anda, Anda akan melakukan sesuatu seperti ini:

Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
WEFX
sumber
5
Anda bahkan tidak perlu membuat kamus baru, cukup tulisDictionary<string, string> dic = SomeList.ToDictionary...
Panagiotis Kanavos
1

Jika Anda tahu Anda tidak akan memiliki kunci duplikat, Anda dapat melakukan:

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

Ini akan memunculkan pengecualian jika ada pasangan kunci / nilai duplikat.

Saya tidak tahu mengapa ini tidak ada dalam kerangka; seharusnya. Tidak ada ketidakpastian; lempar saja pengecualian. Dalam kasus kode ini, itu membuat pengecualian.

toddmo
sumber
Bagaimana jika kamus asli dibuat menggunakan var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);?
mjwills
1

Jangan ragu untuk menggunakan metode ekstensi seperti ini:

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
sumber
0

Berikut adalah solusi alternatif menggunakan 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);
    }
}

Digunakan seperti

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

Seperti yang telah disebutkan orang lain, alasan mengapa Dictionary<TKey,TVal>.AddRangetidak diterapkan adalah karena ada berbagai cara yang mungkin Anda inginkan untuk menangani kasus di mana Anda memiliki duplikat. Ini juga merupakan kasus untuk Collectionantarmuka seperti IDictionary<TKey,TVal>,ICollection<T> , dll

Hanya List<T>mengimplementasikannya, dan Anda akan mencatat bahwa IList<T>antarmuka tidak, karena alasan yang sama: diharapkan perilaku yang saat menambahkan rentang nilai ke koleksi dapat sangat bervariasi, bergantung pada konteks.

Konteks pertanyaan Anda menyarankan Anda tidak khawatir tentang duplikat, dalam hal ini Anda memiliki alternatif satu jalur yang sederhana, menggunakan Linq:

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