在词典和集合上自动添加索引器是一个很好的设计决策吗?

时间:2010-08-27 19:42:09

标签: c# collections dictionary indexing

索引器何时可以自动将项添加到集合/字典中?这是合理的,还是与最佳做法相悖?

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 个答案:

答案 0 :(得分:12)

你应该考虑两个问题 - 这两个问题都表明这是一个坏主意。

首先,继承.NET BCL集合类型通常不是一个好主意。这样做的主要原因是这些类型上的大多数方法(如AddRemove)都不是虚拟的 - 如果您在派生类中提供自己的实现,如果通过它们将不会被调用你的收藏品作为基本类型。在您的情况下,通过隐藏Dictionary<TK,TV>索引器属性,您创建的情况是使用基类引用的调用将执行与使用派生类引用的调用不同的情况...违反{ {3}}:

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!

其次,更重要的是,创建一个在您尝试查找时插入项目的索引器是完全意外的。索引器已经明确定义了getset操作 - 实现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; 
    } 
}

答案 1 :(得分:3)

没有关于你正在做什么的其他信息,对我来说这看起来很令人惊讶。我希望你能从上下文中清楚地说明(即命名为AutoInitializingDictionary或其他什么)。

我个人更喜欢把它变成一个方法而不是索引器;像D.FindOrCreate这样的东西。 (我觉得有一个惯用的名称,这个方法就是我暂时忘记的。)

答案 2 :(得分:3)

我认为这违反了两个原则。 1)最不惊讶的原则。 2)吸气剂不应该改变任何东西。

如果集合中不存在foo,我不希望添加一对{“foo”,null}。

x = collection["Foo"]

答案 3 :(得分:2)

我认为只要这种行为完全清楚就完全没问题了。我有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;

答案 4 :(得分:2)

我担心的主要原因是它不是线程安全的。所有试图可能一次写入字典的多个读者都需要仔细的锁管理,而你最初不会想到(或者说得对)。

答案 5 :(得分:0)

  

索引器什么时候可以接受   自动添加项目到   收集/字典?

从不

  

这是合理的还是违背的   最佳做法?

与最佳做法相反

那就是说,如果这个班级被恰当地命名,那就可以接受了。我个人使用GetOrAdd代替。