Является ли автоматическое добавление индексаторов в словари и коллекции хорошим дизайнерским решением?
когда индексатор может автоматически добавлять элементы в коллекцию / словарь? Разумно ли это или противоречит передовой практике?
public class I { /* snip */ }
public class D : Dictionary<string, I>
{
public I this[string name]
{
get
{
I item;
if (!this.TryGetValue(name, out item))
{
item = new I();
this.Add(name, item);
}
return item;
}
}
}
пример того, как это может использоваться в коллекции:
public class I
{
public I(string name) {/* snip */}
public string Name { get; private set; }
/* snip */
}
public class C : Collection<I>
{
private Dictionary<string, I> nameIndex = new Dictionary<string, I>();
public I this[string name]
{
get
{
I item;
if (!nameIndex.TryGetValue(name, out item))
{
item = new I(name);
this.Add(item); // Will also add the item to nameIndex
}
return item;
}
}
//// Snip: code that manages nameIndex
// protected override void ClearItems()
// protected override void InsertItem(int index, I item)
// protected override void RemoveItem(int index)
// protected override void SetItem(int index, I item)
}
6 ответов
есть две проблемы, которые вы должны рассмотреть - оба из которых предполагают, что это плохая идея.
во-первых, наследование от типов коллекций .NET BCL обычно не является хорошей идеей. Основная причина этого заключается в том, что большинство методов этих типов (например,Add
и Remove
) не являются виртуальными - и если вы предоставляете свои собственные реализации в производном классе, они не будут вызываться, если вы передадите свою коллекцию как базовый тип. В вашем случае, скрывая Dictionary<TK,TV>
свойство индексатора, вы создаете ситуацию, когда вызов с использованием ссылки на базовый класс будет делать что-то другое, чем вызов с использованием ссылки на производный класс ... нарушение Принцип Подстановки Лисков:
var derived = new D();
var firstItem = derived["puppy"]; // adds the puppy entry
var base = (Dictionary<string,I>)derived;
var secondItem = base["kitten"]; // kitten WAS NOT added .. BAD!
во-вторых, и что более важно, создание индексатора, который вставляет элемент при попытке найти один совершенно неожиданный. Индексаторы четко определили get
и set
операции - реализация get
операция по изменению коллекции очень плохая.
для случая, который вы описываете, вам гораздо лучше создать метод расширения, который может работать с любым словарем. Такая операция менее удивительна в том, что она делает, а также не требует создания производного типа коллекции:
public static class DictionaryExtensions
{
public static TValue FindOrAdd<TKey,TValue>(
this IDictionary<TKey,TValue> dictionary, TKey key, TValue value )
where TValue : new()
{
TValue value;
if (!this.TryGetValue(key, out value))
{
value = new TValue();
this.Add(key, value);
}
return value;
}
}
без другой информации о том, что вы делаете, это выглядит как удивительное поведение для меня. Я надеюсь, что вы сделаете это очень ясно из контекста (т. е. назовите его AutoInitializingDictionary
или что-то) что и следовало ожидать.
Я лично предпочел бы сделать это методом, а не индексатором; что-то вроде D.FindOrCreate
. (У меня такое чувство, что есть идиоматическое название метода, который делает это, которое я временно забыл.)
Я бы сказал, что это нарушает два принципа. 1) принцип наименьшего сюрприза. И 2) что геттеры ничего не изменит.
Я не ожидал бы добавить пару {"foo", null}, если foo не существует в colleciton.
x = collection["Foo"]
Я думаю, что это прекрасно, пока это поведение прекрасно понял. У меня 2 класса декораторов:
public class DefaultValueDictionary<K, V> : IDictionary<K, V>
{
public DefaultValueDictionary(IDictionary<K, V> baseDictionary, Func<K, V> defaultValueFunc)
{
...
}
}
и
public class ParameterlessCtorDefaultValueDictionary<K, V>
: DefaultValueDictionary<K, V> where V : new()
{
public ParameterlessCtorDefaultValueDictionary(IDictionary<K, V> baseDictionary)
: base(baseDictionary, k => new V())
{
...
}
}
второй класс идеально подходит для счетчиков и шаблонов, таких как IDictionary<K,List<V>>
;
Я могу сделать
var dict = new ParameterlessCtorDefaultValueDictionary<string, int>();
...
dict[key]++;
вместо трудоемкий:
int count;
if(!dict.TryGetValue(key, out count))
dict[count] = 1;
else dict[count] = count + 1;
главная причина, я бы волновался, что это не потокобезопасными. Несколько читателей, которые пытаются писать в словарь одновременно, потребуют тщательного управления блокировкой, о котором вы вряд ли подумаете (или получите право) сначала.
когда это приемлемо для индексатора автоматическое добавление элементов в a коллекция/словарь?
никогда
это разумно, или наоборот передовая практика?
противоречит лучшим практикам
тем не менее, если класс назван соответствующим образом, это было бы приемлемо. Я бы лично использовал GetOrAdd.