Sözlük'te neden AddRange yok?


115

Başlık yeterince basit, neden yapamıyorum:

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


2
AddRange'a sahip olmayan çok şey var, bu beni her zaman şaşırttı. <> Koleksiyonu gibi. List <> 'in sahip olması her zaman tuhaf görünüyordu, ancak Koleksiyon <> veya diğer IList ve ICollection nesnelerini taşımıyordu.
Tim

39
Burada bir Eric Lippert çekeceğim: "çünkü hiç kimse bu özelliği tasarlamadı, belirledi, uyguladı, test etti, belgeledi ve göndermedi."
Gabe Moothart

3
@ Gabe Moothart - tam olarak varsaydığım buydu. Bu çizgiyi başkalarında kullanmayı seviyorum. Yine de nefret ediyorlar. :)
Tim

2
@GabeMoothart "Çünkü yapamazsın" veya "Çünkü yapamazsın" veya "Çünkü" demek daha kolay olmaz mıydı? - Sanırım bunu söylemek o kadar eğlenceli değil mi? --- (Sanırım alıntılanmış veya başka kelimelerle ifade edilmiş) yanıtınıza sonraki sorum: "Neden hiç kimse bu özelliği tasarlamadı, belirtmedi, uygulamadı, test etmedi, belgelemedi ve göndermedi?" Daha önce önerdiğim yanıtlarla aynı olan "Çünkü kimse yapmadı" diye yanıt vermeye zorlandı. Tahmin edebileceğim diğer tek seçenek alaycı değil ve OP'nin aslında merak ettiği şeydi.
Kod Jokey

Yanıtlar:


76

Orijinal soruya yapılan bir yorum bunu oldukça iyi özetliyor:

çünkü hiç kimse bu özelliği tasarlamadı, belirledi, uyguladı, test etmedi, belgelendirmedi ve göndermedi. - @Gabe Moothart

Neden? Muhtemelen sözlükleri birleştirme davranışı Çerçeve yönergelerine uyacak şekilde gerekçelendirilemez.

AddRangeVeri aralığı yinelenen girişlere izin verdiğinden, bir aralığın ilişkili kapsayıcı için herhangi bir anlamı olmadığı için mevcut değildir. Örneğin, bir IEnumerable<KeyValuePair<K,T>>koleksiyonunuz varsa, yinelenen girişlere karşı koruma sağlamaz.

Bir anahtar-değer çifti koleksiyonu ekleme veya hatta iki sözlüğü birleştirme davranışı basittir. Bununla birlikte, birden çok yinelenen girişle nasıl başa çıkılacağı davranışı değildir.

Bir kopya ile uğraşırken yöntemin davranışı ne olmalıdır?

Aklıma gelen en az üç çözüm var:

  1. yinelenen ilk giriş için bir istisna oluşturun
  2. tüm yinelenen girdileri içeren bir istisna oluşturun
  3. Yinelenenleri yoksay

Bir istisna atıldığında, orijinal sözlüğün durumu ne olmalıdır?

Addneredeyse her zaman atomik bir işlem olarak uygulanır: koleksiyonun durumunu başarılı olur ve günceller veya başarısız olur ve koleksiyonun durumu değişmeden kalır. AddRangeYinelenen hatalar nedeniyle başarısız olabileceği gibi , davranışını tutarlı tutmanın yolu Add, herhangi bir kopyaya bir istisna atarak atomik hale getirmek ve orijinal sözlüğün durumunu değiştirmeden bırakmak olacaktır.

Bir API tüketicisi olarak, yinelenen öğeleri yinelemeli olarak kaldırmak zorunda kalmak sıkıcı olacaktır, bu da tüm yinelenen değerleri AddRangeiçeren tek bir istisna atması gerektiği anlamına gelir .

Seçim daha sonra şu şekilde özetlenir:

  1. Orijinal sözlüğü olduğu gibi bırakarak, tüm kopyalarla bir istisna atın.
  2. Yinelenenleri yok sayın ve devam edin.

Her iki kullanım durumunu da destekleyen argümanlar vardır. Bunu yapmak IgnoreDuplicatesiçin imzaya bir bayrak ekler misiniz ?

IgnoreDuplicates(True olarak ayarlanmış) bayrağı da altta yatan uygulama olarak, önemli bir hız sağlayacaktır olur yinelenen denetimi için kod baypas.

Şimdi, AddRangeher iki durumu da desteklemeye izin veren , ancak belgelenmemiş bir yan etkisi olan bir bayrağınız var (bu, Çerçeve tasarımcılarının kaçınmak için gerçekten çok çalıştığı bir şeydi).

özet

Yinelenenlerle uğraşmak söz konusu olduğunda net, tutarlı ve beklenen bir davranış olmadığından, bunların hepsini bir arada ele almamak ve başlangıç ​​için bir yöntem sağlamamak daha kolaydır.

Kendinizi sürekli olarak sözlükleri birleştirmek zorunda bulursanız, elbette sözlükleri birleştirmek için kendi uzantı yönteminizi yazabilirsiniz; bu, uygulamalarınız için işe yarayacak şekilde davranacaktır.


37
Tamamen yanlış, bir sözlüğün AddRange (IEnumerable <KeyValuePair <K, T >> Değerler) olması gerekir
Gusman

19
Ekle'ye sahip olabilirse, birden çok ekleyebilmelidir. Yinelenen anahtarlara sahip öğeler eklemeye çalıştığınızda davranış, tek bir yinelenen anahtar eklediğinizdeki davranışla aynı olmalıdır.
Uriah Blatherwick

5
AddMultipleuygulamasından farklıdır AddRange, uygulaması riskli olacaktır: Tüm yinelenen anahtarların bir dizisiyle bir istisna mı atarsınız ? Yoksa karşılaştığınız ilk yinelenen anahtara bir istisna mı atarsınız? Bir istisna atılırsa sözlüğün durumu ne olmalıdır? Bozulmamış mı, yoksa başarılı olan tüm anahtarlar mı?
Alan

3
Tamam, öyleyse şimdi numaralarımı manuel olarak yinelemem ve bunları, bahsettiğiniz kopyalar hakkındaki tüm uyarılarla tek tek eklemem gerekiyor. Bunu çerçeveden çıkarmak herhangi bir şeyi nasıl çözer?
doug65536

4
@ doug65536 Eğer API tüketici olarak, şimdi her bir bireyle ne yapmak istediğinize karar verebilirsiniz Çünkü Add- Ya her sarın Addbir de try...catchve yol olduğunu çiftleri yakalamak; veya indeksleyiciyi kullanın ve ilk değerin üzerine daha sonraki değeri yazın; veya ContainsKeydenemeden önce kullanmayı önceden kontrol edin Add, böylece orijinal değeri koruyun. Çerçevenin bir AddRangeveya AddMultipleyöntemi olsaydı , ne olduğunu anlatmanın tek basit yolu bir istisna olabilirdi ve ilgili işleme ve kurtarma daha az karmaşık olmazdı.
Zev Spitz

36

Bir çözümüm var:

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

İyi eğlenceler.


İhtiyacın yok ToList(), sözlük bir IEnumerable<KeyValuePair<TKey,TValue>. Ayrıca, mevcut bir anahtar değeri eklerseniz ikinci ve üçüncü yöntem yöntemleri atar. İyi bir fikir değil mi arıyordun TryAdd? Son olarak, ikincisiWhere(pair->!dic.ContainsKey(pair.Key)...
Panagiotis Kanavos

2
Tamam, ToList()iyi bir çözüm değil , bu yüzden kodu değiştirdim. try { mainDic.AddRange(addDic); } catch { do something }Üçüncü yöntemden emin değilseniz kullanabilirsiniz . İkinci yöntem mükemmel çalışıyor.
ADM-IT

Merhaba Panagiotis Kanavos, umarım mutlusundur.
ADM-IT

1
Teşekkür ederim, bunu çaldım.
metabuddy

14

Benim gibi birinin bu soruyla karşılaşması durumunda - IEnumerable uzantı yöntemlerini kullanarak "AddRange" elde etmek mümkündür:

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

Sözlükleri birleştirirken ana numara, yinelenen anahtarlarla uğraşmaktır. Yukarıdaki kodda bu kısım .Select(grp => grp.First()). Bu durumda, kopya grubundan basitçe ilk öğeyi alır, ancak gerekirse orada daha karmaşık bir mantık uygulayabilirsiniz.


Ya dict1 gelmez varsayılan eşitlik karşılaştırıcısı kullanabilir?
mjwills

Linq yöntemleri, alakalı olduğunda IEqualityComparer'da geçmenize izin verir:var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
Kyle McClellan

12

Tahminim, ne olduğu konusunda kullanıcıya uygun çıktı eksikliği. Sözlüklerde tekrar eden tuşlara sahip olamayacağınıza göre, bazı tuşların kesiştiği iki sözlüğü birleştirmeyi nasıl halledersiniz? Elbette: "Umurumda değil" diyebilirsiniz, ancak bu yanlış döndürme / anahtarları tekrarlamak için bir istisna atma kuralını bozuyor.


5
Bu, aradığınızda anahtar çatışması yaşadığınız yerden ne kadar farklıdır, bunun Adddışında birden fazla olabilir. Kesinlikle aynı ArgumentExceptionşeyi fırlatır Addmı?
nicodemus13

1
@ nicodemus13 Evet, ancak İstisnayı hangi tuşun attığını bilemezsiniz, sadece bu BAZI anahtarın bir tekrar olduğunu.
Gal

1
@Gal verildi, ancak şunları yapabilirsiniz: istisna mesajında ​​çakışan anahtarın adını döndürebilirsiniz (ne yaptığını bilen biri için yararlıdır, herhalde ...), YA DA bunu (parçası?) ParamName argümanı için attığınız ArgumentException argümanı OR-OR, yeni bir istisna türü oluşturabilirsiniz (belki de yeterince genel bir seçenek olabilir NamedElementException??). ... birkaç farklı seçenek diyebilirim
Code Jockey

7

Bunu yapabilirsin

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

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

veya adres aralığı için bir Liste kullanın ve / veya yukarıdaki kalıbı kullanın.

List<KeyValuePair<string, string>>

1
Sözlüğün zaten başka bir sözlüğü kabul eden bir kurucusu var .
Panagiotis Kanavos

1
OP bir sözlüğü klonlamak değil, aralık eklemek istiyor. Örneğimdeki yöntemin adına gelince MethodThatReturnAnotherDic. OP'den geliyor. Lütfen soruyu ve cevabımı tekrar gözden geçirin.
Valamas

1

Yeni bir Sözlükle uğraşıyorsanız (ve kaybedecek mevcut satırlarınız yoksa), her zaman başka bir nesne listesinden ToDictionary () kullanabilirsiniz.

Yani, senin durumunda şöyle bir şey yaparsın:

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

5
Yeni bir sözlük oluşturmanıza bile gerek yok, sadece yazınDictionary<string, string> dic = SomeList.ToDictionary...
Panagiotis Kanavos

1

Yinelenen anahtarlara sahip olmayacağınızı biliyorsanız şunları yapabilirsiniz:

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

Yinelenen bir anahtar / değer çifti varsa bir istisna atar.

Bunun neden çerçevede olmadığını bilmiyorum; olmalı. Belirsizlik yok; sadece bir istisna atın. Bu kod durumunda, bir istisna atar.


Ya orijinal sözlük kullanılarak oluşturulmuşsa var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);?
mjwills

1

Şu şekilde bir uzatma yöntemi kullanmaktan çekinmeyin:

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

Burada c # 7 ValueTuples (tuple değişmezleri) kullanan alternatif bir çözüm var

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

Gibi kullanılmış

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

0

Başkalarının da belirttiği gibi, Dictionary<TKey,TVal>.AddRangeuygulanmamasının nedeni, yinelenen vakaları ele almak isteyebileceğiniz çeşitli yolların olmasıdır. Bu aynı zamanda böyledir Collectionya arayüzleri gibi IDictionary<TKey,TVal>, ICollection<T>vb

Yalnızca List<T>uygular IList<T>ve aynı nedenlerden dolayı arabirimin olmadığını unutmayın : Bir koleksiyona bir dizi değer eklerken beklenen davranış, bağlama bağlı olarak geniş ölçüde değişebilir.

Sorunuzun bağlamı, kopyalar hakkında endişelenmediğinizi gösteriyor, bu durumda Linq kullanarak basit bir oneliner alternatifiniz var:

MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.