Когда для индексатора допустимо автоматическое добавление элементов в коллекцию / словарь? Разумно ли это или противоречит передовому опыту?
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)
}
Я бы сказал, что это нарушает два принципа. 1) принцип наименьшего удивления. И 2) что геттеры не должны ничего менять.
Я бы не стал добавлять пару {"foo", null}, если foo не существует в коллекции.
x = collection["Foo"]
Без какой-либо другой информации о том, что вы делаете, такое поведение кажется мне удивительным. Я надеюсь, что вы ясно из контекста (например, назовите его AutoInitializingDictionary
или что-то в этом роде), чего следует ожидать.
Лично я предпочел бы сделать это методом, а не индексатором; что-то вроде D.FindOrCreate
. (У меня такое чувство, что есть идиоматическое название для метода, который делает это, но я временно забыл.)
Я думаю, что это совершенно нормально, пока такое поведение совершенно ясно. У меня есть 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
;
Я могу сделать
var dict = new ParameterlessCtorDefaultValueDictionary<string, int>();
...
dict[key]++;
вместо утомительного:
int count;
if(!dict.TryGetValue(key, out count))
dict[count] = 1;
else dict[count] = count + 1;
Основная причина, по которой я был бы обеспокоен, заключается в том, что это не будет потокобезопасным. Несколько читателей, пытающихся одновременно писать в Словарь, потребуют тщательного управления блокировками, о котором вы, вероятно, не подумали бы (или не поняли бы правильно) поначалу.