Какой механизм является лучшим способом расширить словарь для устранения недостающих ключей и почему?
Есть небольшое раздражение, которое я нахожу с большим количеством - у меня есть Dictionary<TKey, TValue>
, который содержит значения, которые могут быть или не быть.
Таким образом, нормальное поведение будет заключаться в использовании индексатора, например:
object result = myDictionary["key"];
Однако, если "key"
не находится в словаре, это бросает a KeyNotFoundException
, поэтому вы делаете это вместо:
object val;
if (!myDictionary.TryGetValue("key", out val))
{
val = ifNotFound;
}
Это нормально, за исключением того, что я могу загрузить их в ряд - TryGetValue
начинает чувствовать себя ужасно неуклюже.
Итак, опция 1 - это метод расширения:
public static TValue TryGet<TKey, TValue>(
this Dictionary<TKey, TValue> input,
TKey key,
TValue ifNotFound = default(TValue))
{
TValue val;
if (input.TryGetValue(key, out val))
{
return val;
}
return ifNotFound;
}
Что позволяет мне делать:
object result = myDictionary.TryGet("key1") ?? ifNotFound;
int i = anotherDictionary.TryGet("key2", -1);
Это достаточно просто, но дополнительный метод расширения с именем, похожим на существующие методы экземпляра, потенциально добавляет путаницу и снижает ремонтопригодность. Он также не согласуется с набором индексатора словарей, который будет обрабатывать отсутствующие ключи.
Таким образом, опция 2 представляет собой новую реализацию IDictionary<TKey, TValue>
с неявным отрисовкой из Dictionary<TKey, TValue>
, но индексом, который возвращает default(TValue)
вместо того, чтобы бросать KeyNotFoundException
.
Что мне делать:
ForgivingDictionary<string, object> dict = myDictionary;
object val = dict["key"] ?? ifNotFound;
// do stuff to val, then...
dict["key"] = val;
Итак, значения get и set согласованы, но типы значений более грязные, а ForgivingDictionary
содержит намного больше кода.
Оба метода кажутся "грязными" - есть ли лучший способ сделать это уже в .Net?
Оба метода делают компромиссы, которые могут вызвать путаницу, но являются ли еще очевидными/ясными, чем другие? И почему?
Ответы
Ответ 1
При назначении имени метода расширения, предназначенного для замены существующего метода, я обычно добавляю к имени метода для специфичности, а не сокращаю его:
GetValueOrDefault(...)
Что касается ForgivingDictionary
, вы можете ограничить TKey
так, чтобы он не мог быть типом значения. Однако, если вы должны иметь дело с типами значений в нем, вы вернете что-то для типа значения, и лучший вариант - вернуть default(TKey)
, так как вы не можете вернуть null
.
Честно говоря, я бы пошел с расширением.
Изменить: GetValueOrDefault()
, конечно, не добавит в словарь, если он не найдет ключ. Я бы просто вернул значение по умолчанию, если оно не было найдено, потому что оно было названо. Если бы кто-то хотел его вставить, хорошее имя было бы GetValueOrInsertDefault()
.
Ответ 2
Я не могу сделать вывод из вашего вопроса, что делать, если ключ не найден. Я могу представить, что в этом случае ничего не должно быть сделано, но я также могу представить себе обратное.
Во всяком случае, изящная альтернатива для ряда этих инструкций TryGetValue, которые вы описываете, использует один из следующих методов расширения. Я предоставил два варианта, в зависимости от того, что нужно делать или нет, когда словарь не содержит ключ:
/// <summary> Iterates over all values corresponding to the specified keys,
///for which the key is found in the dictionary. </summary>
public static IEnumerable<TValue> TryGetValues<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, IEnumerable<TKey> keys)
{
TValue value;
foreach (TKey key in keys)
if (dictionary.TryGetValue(key, out value))
yield return value;
}
/// <summary> Iterates over all values corresponding to the specified keys,
///for which the key is found in the dictionary. A function can be specified to handle not finding a key. </summary>
public static IEnumerable<TValue> TryGetValues<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, IEnumerable<TKey> keys, Action<TKey> notFoundHandler)
{
TValue value;
foreach (TKey key in keys)
if (dictionary.TryGetValue(key, out value))
yield return value;
else
notFoundHandler(key);
}
Пример кода о том, как это использовать:
TKey[] keys = new TKey{...};
foreach(TValue value in dictionary.TryGetValues(keys))
{
//some action on all values here
}
Ответ 3
Или, возможно,
public static TValue TryGet<TKey, TValue>(this Dictionary<TKey, TValue> input,
TKey key)
{
return input.ContainsKey(key) ? input[key] : *some default value*;
}