Создайте словарь с LINQ и не добавьте "элемент с тем же ключом уже добавлен"
Я хочу найти ключ в словаре и заменить значение, если оно найдено, или добавить ключ/значение, если это не так.
код:
public class MyObject
{
public string UniqueKey { get; set; }
public string Field1 { get; set; }
public string Field2 { get; set; }
}
LINQ Solution (бросает An item with the same key has already been added.
):
Dictionary<string, MyObject> objectDict = csvEntries.ToDictionary(csvEntry => csvEntry.ToMyObject().UniqueKey, csvEntry => csvEntry.ToMyObject());
Решение ForEach (работает):
Dictionary<string, MyObject> objectDict = new Dictionary<string, MyObject>();
foreach (CSVEntry csvEntry in csvEntries)
{
MyObject obj = csvEntry.ToMyObject();
if (objectDict.ContainsKey(obj.UniqueKey))
{
objectDict[obj.UniqueKey] = obj;
}
else {
objectDict.Add(obj.UniqueKey, obj);
}
}
Мне действительно понравилось решение LINQ, но, поскольку оно стоит, оно выдает вышеупомянутую ошибку. Есть ли хороший способ избежать ошибки и использовать LINQ?
Ответы
Ответ 1
Вы можете использовать GroupBy
для создания уникальных ключей:
Dictionary<string, MyObject> objectDict = csvEntries
.Select(csvEntry => csvEntry.ToMyObject())
.GroupBy(x => x.UniqueKey)
.ToDictionary(grp => grp.Key, grp => grp.First());
Однако вместо grp.First()
вы можете создать коллекцию с ToList
или ToArray
. Таким образом, вы не берете суровый объект в случае дублирования ключей.
Другой вариант - использовать Lookup<TKey, TValue>
, который позволяет дублировать ключи и даже несуществующие ключи, в этом случае вы получите пустую последовательность.
var uniqueKeyLookup = csvEntries
.Select(csvEntry => csvEntry.ToMyObject())
.ToLookup(x => x.UniqueKey);
IEnumerable<MyObject> objectsFor1234 = uniqueKeyLookup["1234"]; // empty if it doesn't exist
Ответ 2
Опираясь на ответ Rango, вот метод расширения, который вы можете использовать, чтобы вам не нужно было дублировать реализацию во всем проекте:
public static class DictionaryExtensions
{
public static Dictionary<TKey, TValue> ToDictionaryWithDupSelector<TKey, TValue>(
this IEnumerable<TValue> enumerable,
Func<TValue, TKey> groupBy, Func<IEnumerable<TValue>, TValue> selector = null) {
if (selector == null)
selector = new Func<IEnumerable<TValue>, TValue>(grp => grp.First());
return enumerable
.GroupBy(e => groupBy(e))
.ToDictionary(grp => grp.Key, grp => selector(grp));
}
}
По умолчанию он выберет первый элемент при наличии дубликатов, но я предоставил необязательный параметр, в котором вы можете указать альтернативный селектор. Пример вызова метода расширения:
var objList = new List<string[]> {
new string[2] {"1", "first"},
new string[2] {"1", "last"},
new string[2] {"2", "you"},
};
var asDict = objList.ToDictionary(
arr => arr[0],
grp => grp.Last()
);
Ответ 3
Я хочу найти ключ в словаре и заменить значение, если оно найдено, или добавить ключ/значение, если это не так
В принципе, вы хотите использовать ToDictionary
где вы берете последнее вхождение объекта с тем же ключом. Предполагая, что csvEntiries
равен IEnumerable<MyObject>
, можно сделать следующее:
Dictionary<string, MyObject> objectDict = csvEntries
.Reverse()
.Distinct(new MyObjectComparer())
.ToDictionary(x => x.UniqueKey);
где MyObjectComparer
public class MyObjectComparer : IEqualityComparer<MyObject>
{
public bool Equals(MyObject x, MyObject y)
=> x.UniqueKey == y.UniqueKey;
public int GetHashCode(MyObject obj)
=> obj.UniqueKey.GetHashCode();
}