Являются ли автоматическое добавление индексаторов в словари и коллекции хорошим дизайнерским решением? - PullRequest
8 голосов
/ 27 августа 2010

Когда для индексатора допустимо автоматическое добавление элементов в коллекцию / словарь? Это разумно или противоречит передовому опыту?

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 ]

12 голосов
/ 28 августа 2010

Есть две проблемы, которые вы должны рассмотреть, обе из которых предполагают, что это плохая идея.

Во-первых, наследование от типов коллекций .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; 
    } 
}
3 голосов
/ 28 августа 2010

Я бы сказал, что это нарушает два принципа. 1) принцип наименьшего удивления. И 2) что добытчики не должны ничего менять.

Я не ожидал бы добавить пару {"foo", null}, если foo не существует в коллекции.

x = collection["Foo"]
3 голосов
/ 27 августа 2010

Без другой информации о том, что ты делаешь, это выглядит для меня как удивительное поведение.Я надеюсь, что вы очень ясно из контекста (то есть назовете его AutoInitializingDictionary или что-то в этом роде) того, что следует ожидать.

Я бы лично предпочел сделать это методом, а не индексатором;что-то вроде D.FindOrCreate.(У меня такое ощущение, что для этого метода есть идиоматическое название, о котором я временно забыл.)

2 голосов
/ 30 августа 2010

Основная причина, по которой я был бы обеспокоен, состоит в том, что это не было бы потокобезопасным. Многим читателям, пытающимся, возможно, написать в Словарь одновременно, потребуется тщательное управление блокировками, о котором вы, скорее всего, не думаете (или не получаете права) вначале.

2 голосов
/ 27 августа 2010

Я думаю, что это прекрасно, если это поведение совершенно ясно.У меня есть 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;
0 голосов
/ 30 августа 2010

Когда для индексатора допустимо автоматическое добавление элементов в коллекцию / словарь?

Никогда

Это разумно или противоречит лучшимпрактика?

Вопреки лучшим практикам

Тем не менее, если класс будет назван соответствующим образом, он будет приемлемым.Я бы лично использовал GetOrAdd .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...