ชื่อเรื่องเป็นพื้นฐานเพียงพอทำไมฉันไม่:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
ชื่อเรื่องเป็นพื้นฐานเพียงพอทำไมฉันไม่:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
คำตอบ:
ความคิดเห็นของคำถามเดิมสรุปได้ค่อนข้างดี:
เนื่องจากไม่เคยมีใครออกแบบระบุใช้งานทดสอบจัดทำเอกสารและจัดส่งคุณลักษณะนั้น - @Gabe Moothart
ทำไม? อาจเป็นเพราะพฤติกรรมของการรวมพจนานุกรมไม่สามารถให้เหตุผลในลักษณะที่สอดคล้องกับแนวทางของ Framework ได้
AddRange
ไม่มีอยู่เนื่องจากช่วงไม่มีความหมายใด ๆ กับคอนเทนเนอร์ที่เชื่อมโยงเนื่องจากช่วงของข้อมูลอนุญาตให้มีรายการที่ซ้ำกัน เช่นหากคุณมีIEnumerable<KeyValuePair<K,T>>
คอลเล็กชันนั้นจะไม่ป้องกันรายการที่ซ้ำกัน
ลักษณะการทำงานของการเพิ่มคอลเลกชันของคู่คีย์ - ค่าหรือแม้กระทั่งการรวมพจนานุกรมสองพจนานุกรมก็ตรงไปตรงมา อย่างไรก็ตามพฤติกรรมในการจัดการกับรายการที่ซ้ำกันหลายรายการไม่เป็นเช่นนั้น
พฤติกรรมของเมธอดควรเป็นอย่างไรเมื่อเกี่ยวข้องกับรายการที่ซ้ำกัน?
มีอย่างน้อยสามวิธีที่ฉันคิดได้:
เมื่อเกิดข้อยกเว้นสถานะของพจนานุกรมต้นฉบับควรเป็นอย่างไร?
Add
มักจะถูกนำไปใช้เป็นการดำเนินการแบบปรมาณู: มันประสบความสำเร็จและอัปเดตสถานะของคอลเลกชันหรือล้มเหลวและสถานะของคอลเลกชันจะไม่เปลี่ยนแปลง เนื่องจากAddRange
อาจล้มเหลวเนื่องจากข้อผิดพลาดที่ซ้ำกันวิธีที่จะทำให้พฤติกรรมของมันสอดคล้องกันAdd
ก็คือการทำให้เป็นปรมาณูด้วยการทิ้งข้อยกเว้นสำหรับสิ่งที่ซ้ำกันและปล่อยให้สถานะของพจนานุกรมต้นฉบับไม่เปลี่ยนแปลง
ในฐานะที่เป็นผู้บริโภค API ก็จะน่าเบื่อที่จะต้องซ้ำลบองค์ประกอบที่ซ้ำกันซึ่งหมายถึงว่าAddRange
ควรโยนข้อยกเว้นเดียวที่มีทุกค่าที่ซ้ำกัน
ทางเลือกจะเดือดลงไปที่:
มีข้อโต้แย้งในการสนับสนุนทั้งกรณีการใช้งาน ในการทำเช่นนั้นคุณเพิ่มIgnoreDuplicates
ธงลงในลายเซ็นหรือไม่?
การIgnoreDuplicates
ตั้งค่าสถานะ (เมื่อตั้งค่าเป็นจริง) จะช่วยเพิ่มความเร็วอย่างมีนัยสำคัญเนื่องจากการใช้งานพื้นฐานจะข้ามรหัสสำหรับการตรวจสอบซ้ำ
ตอนนี้คุณมีแฟล็กที่อนุญาตให้AddRange
รองรับทั้งสองกรณี แต่มีผลข้างเคียงที่ไม่มีเอกสาร (ซึ่งเป็นสิ่งที่นักออกแบบ Framework ทำงานอย่างหนักเพื่อหลีกเลี่ยง)
สรุป
เนื่องจากไม่มีพฤติกรรมที่ชัดเจนสอดคล้องและคาดหวังเมื่อต้องจัดการกับรายการที่ซ้ำกันจึงง่ายกว่าที่จะไม่จัดการกับสิ่งเหล่านี้ทั้งหมดเข้าด้วยกันและไม่ได้ระบุวิธีการเริ่มต้นด้วย
หากคุณพบว่าตัวเองต้องรวมพจนานุกรมอย่างต่อเนื่องคุณสามารถเขียนวิธีการขยายของคุณเองเพื่อรวมพจนานุกรมซึ่งจะทำงานในลักษณะที่เหมาะกับแอปพลิเคชันของคุณ
AddMultiple
แตกต่างจากที่AddRange
ไม่ว่าการใช้งานจะเป็นไปได้: คุณโยนข้อยกเว้นด้วยอาร์เรย์ของคีย์ที่ซ้ำกันทั้งหมดหรือไม่? หรือคุณโยนข้อยกเว้นให้กับคีย์ที่ซ้ำกันครั้งแรกที่คุณพบ? สถานะของพจนานุกรมควรเป็นอย่างไรหากมีข้อยกเว้น Pristine หรือกุญแจทั้งหมดที่ทำสำเร็จ?
Add
- ทั้งห่อแต่ละAdd
ในtry...catch
และจับซ้ำกันว่าวิธีการ; หรือใช้ตัวสร้างดัชนีและเขียนทับค่าแรกด้วยค่าในภายหลัง หรือตรวจสอบล่วงหน้าโดยใช้ContainsKey
ก่อนที่จะพยายามAdd
รักษาค่าดั้งเดิมไว้ หากเฟรมเวิร์กมีAddRange
หรือAddMultiple
วิธีการวิธีง่ายๆเพียงวิธีเดียวในการสื่อสารสิ่งที่เกิดขึ้นจะต้องผ่านข้อยกเว้นและการจัดการและการกู้คืนที่เกี่ยวข้องจะไม่ซับซ้อนน้อยลง
ฉันมีวิธีแก้ปัญหา:
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>
ไฟล์. นอกจากนี้วิธีการที่สองและสามจะเกิดขึ้นหากคุณเพิ่มค่าคีย์ที่มีอยู่ ไม่ใช่ความคิดที่ดีคุณกำลังมองหาTryAdd
? สุดท้ายตัวที่สองสามารถแทนที่ด้วยWhere(pair->!dic.ContainsKey(pair.Key)...
ToList()
ไม่ใช่วิธีแก้ปัญหาที่ดีฉันจึงเปลี่ยนรหัส คุณสามารถใช้ได้try { mainDic.AddRange(addDic); } catch { do something }
หากคุณไม่แน่ใจสำหรับวิธีที่สาม วิธีที่สองทำงานได้อย่างสมบูรณ์
ในกรณีที่มีคนเจอคำถามนี้เหมือนตัวเอง - เป็นไปได้ที่จะบรรลุ "AddRange" โดยใช้วิธีการขยาย IEnumerable:
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
ไม่ใช้ตัวเปรียบเทียบความเท่าเทียมเริ่มต้น
var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
ฉันเดาว่าไม่มีผลลัพธ์ที่เหมาะสมสำหรับผู้ใช้ว่าเกิดอะไรขึ้น เนื่องจากคุณไม่สามารถมีคีย์ซ้ำในพจนานุกรมได้คุณจะจัดการกับการรวมพจนานุกรมสองชุดโดยที่บางคีย์ตัดกันได้อย่างไร แน่นอนว่าคุณสามารถพูดว่า: "ฉันไม่สนใจ" แต่นั่นเป็นการทำลายแบบแผนในการส่งคืนเท็จ / โยนข้อยกเว้นสำหรับการกดคีย์ซ้ำ
Add
อย่างไรนอกเหนือจากนั้นอาจเกิดขึ้นได้มากกว่าหนึ่งครั้ง มันจะโยนเดียวArgumentException
ที่Add
ไม่แน่นอน?
NamedElementException
??) โยนแทนหรือเป็น innerException ของ ArgumentException ซึ่งระบุองค์ประกอบที่ตั้งชื่อที่ขัดแย้งกัน ... ตัวเลือกที่แตกต่างกันเล็กน้อยฉันจะบอกว่า
คุณสามารถทำได้
Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);
public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
dic.Add(.., ..);
}
หรือใช้ List สำหรับ addrange และ / หรือใช้รูปแบบด้านบน
List<KeyValuePair<string, string>>
MethodThatReturnAnotherDic
ในฐานะที่เป็นชื่อของวิธีในตัวอย่างของฉัน มันมาจาก OP โปรดตรวจสอบคำถามและคำตอบของฉันอีกครั้ง
หากคุณกำลังจัดการกับพจนานุกรมใหม่ (และคุณไม่มีแถวที่จะสูญเสีย) คุณสามารถใช้ ToDictionary () จากรายการวัตถุอื่นได้ตลอดเวลา
ดังนั้นในกรณีของคุณคุณจะทำสิ่งนี้:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
Dictionary<string, string> dic = SomeList.ToDictionary...
หากคุณรู้ว่าคุณจะไม่มีคีย์ซ้ำคุณสามารถทำได้:
dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
จะทำให้เกิดข้อยกเว้นหากมีคู่คีย์ / ค่าที่ซ้ำกัน
ฉันไม่รู้ว่าทำไมสิ่งนี้ถึงไม่อยู่ในกรอบ ควรจะเป็น. ไม่มีความแน่นอน เพียงแค่โยนข้อยกเว้น ในกรณีของรหัสนี้จะมีข้อยกเว้น
var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);
?
อย่าลังเลที่จะใช้วิธีการขยายเช่นนี้:
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;
}
นี่คือทางเลือกอื่นโดยใช้ 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);
}
}
ใช้แล้วชอบ
segments
.Zip(values, (s, v) => (s.AsSpan().StartsWith("{") ? s.Trim('{', '}') : null, v))
.Where(zip => zip.Item1 != null)
.AddTo(queryParams);
ดังที่คนอื่น ๆ ได้กล่าวถึงสาเหตุที่Dictionary<TKey,TVal>.AddRange
ไม่นำมาใช้เนื่องจากมีหลายวิธีที่คุณอาจต้องการจัดการกรณีที่คุณมีรายการซ้ำ และนี่ก็เป็นกรณีที่Collection
หรือการเชื่อมต่อเช่นIDictionary<TKey,TVal>
,ICollection<T>
ฯลฯ
List<T>
ใช้งานได้เท่านั้นและคุณจะทราบว่าIList<T>
อินเทอร์เฟซไม่ทำงานด้วยเหตุผลเดียวกัน: ที่คาดไว้ลักษณะการทำงานที่เมื่อเพิ่มช่วงของค่าลงในคอลเล็กชันอาจแตกต่างกันไปในวงกว้างขึ้นอยู่กับบริบท
บริบทของคำถามของคุณแสดงให้เห็นว่าคุณไม่ต้องกังวลเกี่ยวกับรายการที่ซ้ำกันซึ่งในกรณีนี้คุณมีทางเลือกในการใช้งานง่ายโดยใช้ Linq:
MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));