繁体   English   中英

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

[英]Are auto-adding indexers on Dictionaries and Collections a good design decision?

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

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)
}

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

首先,继承.NET BCL集合类型通常不是一个好主意。 这样做的主要原因是这些类型上的大多数方法(如AddRemove )都不是虚拟的 - 如果您在派生类中提供自己的实现,那么如果您将集合作为基类型传递,则不会调用它们。 在您的情况下,通过隐藏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!

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

没有关于你正在做什么的其他信息,这对我来说似乎是令人惊讶的行为。 我希望你从上下文( AutoInitializingDictionary它命名为AutoInitializingDictionary或其他东西)中清楚地AutoInitializingDictionary出什么是预期的。

我个人更喜欢把它作为方法而不是索引器; D.FindOrCreate这样的D.FindOrCreate (我觉得有一个惯用的名称,这个方法可以做到这一点,我暂时忘记了。)

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

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

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;

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

索引器何时可以自动将项添加到集合/字典中?

决不

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

与最佳做法相反

也就是说,如果课程名称恰当,那就可以接受了。 我个人会使用GetOrAdd

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM