Tiêu đề là đủ cơ bản, tại sao tôi không thể:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Tiêu đề là đủ cơ bản, tại sao tôi không thể:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Câu trả lời:
Một nhận xét cho câu hỏi ban đầu tóm tắt điều này khá tốt:
bởi vì không ai từng thiết kế, chỉ định, triển khai, thử nghiệm, ghi lại và vận chuyển tính năng đó. - @Gabe Moothart
Tại sao? Chà, có thể là do hành vi hợp nhất các từ điển không thể được lý giải theo cách phù hợp với các nguyên tắc của Khung.
AddRange
không tồn tại bởi vì một phạm vi không có bất kỳ ý nghĩa nào đối với một vùng chứa kết hợp, vì phạm vi dữ liệu cho phép các mục nhập trùng lặp. Ví dụ: nếu bạn có một IEnumerable<KeyValuePair<K,T>>
bộ sưu tập không bảo vệ khỏi các mục trùng lặp.
Hành vi thêm một bộ sưu tập các cặp khóa-giá trị hoặc thậm chí hợp nhất hai từ điển là dễ dàng. Tuy nhiên, hành vi của cách đối phó với nhiều mục nhập trùng lặp thì không.
Hành vi của phương thức sẽ như thế nào khi nó xử lý một bản sao?
Có ít nhất ba giải pháp tôi có thể nghĩ đến:
Khi một ngoại lệ được ném ra, trạng thái của từ điển gốc sẽ như thế nào?
Add
hầu như luôn luôn được thực hiện như một hoạt động nguyên tử: nó thành công và cập nhật trạng thái của bộ sưu tập, hoặc nó không thành công và trạng thái của bộ sưu tập được giữ nguyên. Có AddRange
thể không thành công do lỗi trùng lặp, cách để giữ cho hành vi của nó nhất quán Add
là cũng làm cho nó trở thành nguyên tử bằng cách ném một ngoại lệ vào bất kỳ bản sao nào và giữ nguyên trạng thái của từ điển gốc là không thay đổi.
Là một người tiêu dùng API, sẽ thật tẻ nhạt nếu phải xóa lặp đi lặp lại các phần tử trùng lặp, điều này ngụ ý rằng AddRange
nên ném một ngoại lệ duy nhất chứa tất cả các giá trị trùng lặp.
Sự lựa chọn sau đó sẽ tóm tắt thành:
Có những lập luận ủng hộ cả hai trường hợp sử dụng. Để làm điều đó, bạn có thêm một IgnoreDuplicates
lá cờ vào chữ ký không?
Các IgnoreDuplicates
lá cờ (khi thiết lập là true) cũng sẽ cung cấp một tốc độ đáng kể lên, như việc thực hiện cơ bản sẽ bỏ qua các mã để kiểm tra trùng lặp.
Vì vậy, bây giờ, bạn có một cờ cho phép AddRange
hỗ trợ cả hai trường hợp, nhưng có tác dụng phụ không có tài liệu (đó là điều mà các nhà thiết kế Framework đã làm việc rất vất vả để tránh).
Tóm lược
Vì không có hành vi rõ ràng, nhất quán và được mong đợi khi xử lý các bản sao, nên việc không giải quyết tất cả chúng cùng nhau và không cung cấp phương pháp để bắt đầu sẽ dễ dàng hơn.
Nếu bạn thấy mình liên tục phải kết hợp các từ điển, tất nhiên bạn có thể viết phương thức mở rộng của riêng mình để hợp nhất các từ điển, phương thức này sẽ hoạt động theo cách hoạt động cho (các) ứng dụng của bạn.
AddMultiple
khác với AddRange
, bất kể việc triển khai nó sẽ trở nên khó khăn: Bạn có ném một ngoại lệ với một mảng tất cả các khóa trùng lặp không? Hay bạn ném một ngoại lệ trên khóa trùng lặp đầu tiên mà bạn gặp phải? Trạng thái của từ điển sẽ như thế nào nếu một ngoại lệ được ném ra? Nguyên sơ, hay tất cả các chìa khóa đã thành công?
Add
- hoặc gói từng người Add
trong một try...catch
và bắt các bản sao theo cách đó; hoặc sử dụng trình lập chỉ mục và ghi đè giá trị đầu tiên bằng giá trị sau đó; hoặc kiểm tra ContainsKey
trước bằng cách sử dụng trước khi thử Add
, do đó bảo toàn giá trị ban đầu. Nếu khung công tác có một AddRange
hoặc AddMultiple
phương pháp, thì cách đơn giản duy nhất để truyền đạt những gì đã xảy ra là thông qua một ngoại lệ, và việc xử lý và khôi phục liên quan sẽ không kém phần phức tạp.
Tôi có một số giải pháp:
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;
}
}
}
}
Chúc vui vẻ.
ToList()
, từ điển là một IEnumerable<KeyValuePair<TKey,TValue>
. Ngoài ra, các phương thức phương thức thứ hai và thứ ba sẽ ném nếu bạn thêm một giá trị khóa hiện có. Bạn đang tìm kiếm không phải là một ý tưởng TryAdd
hay? Cuối cùng, thứ hai có thể được thay thế bằngWhere(pair->!dic.ContainsKey(pair.Key)...
ToList()
không phải là giải pháp tốt nên tôi đã thay đổi mã. Bạn có thể sử dụng try { mainDic.AddRange(addDic); } catch { do something }
nếu bạn không chắc chắn về phương pháp thứ ba. Phương pháp thứ hai hoạt động hoàn hảo.
Trong trường hợp ai đó gặp câu hỏi này giống như tôi - bạn có thể đạt được "AddRange" bằng cách sử dụng các phương thức mở rộng IEnumerable:
var combined =
dict1.Union(dict2)
.GroupBy(kvp => kvp.Key)
.Select(grp => grp.First())
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Thủ thuật chính khi kết hợp các từ điển là xử lý các khóa trùng lặp. Trong đoạn mã trên, đó là một phần .Select(grp => grp.First())
. Trong trường hợp này, nó chỉ cần lấy phần tử đầu tiên từ nhóm các bản sao nhưng bạn có thể triển khai logic phức tạp hơn ở đó nếu cần.
dict1
không sử dụng trình so sánh bình đẳng mặc định?
var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
Tôi đoán là thiếu đầu ra thích hợp cho người dùng về những gì đã xảy ra. Vì bạn không thể có các khóa lặp lại trong từ điển, bạn sẽ xử lý như thế nào khi hợp nhất hai từ điển trong đó một số khóa giao nhau? Chắc chắn bạn có thể nói: "Tôi không quan tâm" nhưng điều đó phá vỡ quy ước trả về false / ném một ngoại lệ cho các khóa lặp lại.
Add
, ngoài việc nó có thể xảy ra nhiều lần. Nó sẽ ném cùng ArgumentException
mà Add
không, chắc chắn?
NamedElementException
??), được ném thay vì hoặc là bên trong của một ArgumentException, chỉ định phần tử được đặt tên xung đột ... một vài lựa chọn khác nhau, tôi muốn nói
Bạn có thể làm điều này
Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);
public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
dic.Add(.., ..);
}
hoặc sử dụng Danh sách cho dải bổ sung và / hoặc sử dụng mẫu ở trên.
List<KeyValuePair<string, string>>
MethodThatReturnAnotherDic
. Nó đến từ OP. Vui lòng xem lại câu hỏi và câu trả lời của tôi một lần nữa.
Nếu bạn đang xử lý một Từ điển mới (và bạn không có hàng hiện có để mất), bạn luôn có thể sử dụng ToDictionary () từ một danh sách các đối tượng khác.
Vì vậy, trong trường hợp của bạn, bạn sẽ làm như sau:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
Dictionary<string, string> dic = SomeList.ToDictionary...
Nếu bạn biết mình sẽ không có các khóa trùng lặp, bạn có thể làm:
dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Nó sẽ ném ra một ngoại lệ nếu có một cặp khóa / giá trị trùng lặp.
Tôi không biết tại sao điều này không có trong khuôn khổ; nên là. Không có gì không chắc chắn; chỉ cần ném một ngoại lệ. Trong trường hợp của mã này, nó ném một ngoại lệ.
var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);
?
Hãy sử dụng phương pháp mở rộng như thế này:
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;
}
Đây là một giải pháp thay thế bằng cách sử dụng c # 7 ValueTuples (tuple chữ)
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);
}
}
Được sử dụng như
segments
.Zip(values, (s, v) => (s.AsSpan().StartsWith("{") ? s.Trim('{', '}') : null, v))
.Where(zip => zip.Item1 != null)
.AddTo(queryParams);
Như những người khác đã đề cập, lý do tại sao Dictionary<TKey,TVal>.AddRange
không được triển khai là vì có nhiều cách khác nhau mà bạn có thể muốn xử lý các trường hợp bạn có bản sao. Đây cũng là trường hợp cho Collection
hoặc giao diện như IDictionary<TKey,TVal>
, ICollection<T>
vv
Chỉ List<T>
triển khai nó và bạn sẽ lưu ý rằng IList<T>
giao diện không, vì những lý do tương tự: hành vi mong đợi khi thêm một loạt giá trị vào một tập hợp có thể rất khác nhau, tùy thuộc vào ngữ cảnh.
Bối cảnh câu hỏi của bạn cho thấy bạn không lo lắng về các bản sao, trong trường hợp đó, bạn có một giải pháp thay thế oneliner đơn giản, sử dụng Linq:
MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));