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

chi*_*emp 8 c# indexing collections dictionary

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

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如何在集合中使用它的示例:

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)
}
Run Code Online (Sandbox Code Playgroud)

LBu*_*kin 12

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

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

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!
Run Code Online (Sandbox Code Playgroud)

其次,更重要的是,创建一个在尝试查找项目时插入项目的索引器是完全出乎意料的.索引器已经明确定义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; 
    } 
}
Run Code Online (Sandbox Code Playgroud)