11

很多时候,我需要一组带有数字标识符的非顺序对象。我喜欢为此使用 KeyedCollection,但我认为有一个严重的缺点。如果使用 int 作为键,则不能再通过索引访问集合成员(collection[index] 现在实际上是 collection[key])。这是一个足够严重的问题来避免使用 int 作为键吗?更可取的选择是什么?(也许是 int.ToString()?)

我以前这样做过,没有任何重大问题,但最近我遇到了一个令人讨厌的问题,如果密钥是 int,则针对 KeyedCollection 的 XML 序列化不起作用,这是由于.NET 中的一个错误

4

4 回答 4

7

基本上,您需要确定该类的用户是否可能会因为他们不能这样做而感到困惑,例如:

for(int i=0; i=< myCollection.Count; i++)
{
    ... myCollection[i] ...
}

尽管他们当然可以使用 foreach 或使用演员表:

for(int i=0; i=< myCollection.Count; i++)
{
    ... ((Collection<MyType>)myCollection)[i] ...
}

这不是一个容易的决定,因为它很容易导致 heisenbugs。我决定在我的一个应用程序中允许它,该类用户几乎完全通过密钥访问。

我不确定我是否会为共享类库这样做:一般来说,我会避免在公共 API 中公开 KeyedCollection:相反,我会在公共 API 中公开 IList<T>,并且 API 的使用者需要键控访问可以使用构造函数定义自己的内部 KeyedCollection,该构造函数采用 IEnumerable<TItem> 并用它填充集合。这意味着您可以轻松地从从 API 检索的列表中构建新的 KeyedCollection。

关于序列化,我向 Microsoft Connect 报告了一个性能问题:KeyedCollection 维护一个内部字典和一个列表,并序列化两者 - 序列化列表就足够了,因为可以轻松地在反序列化时重新创建字典。

由于这个原因以及 XmlSerialization 错误,我建议您避免序列化 KeyedCollection - 而只序列化 KeyedCollection.Items 列表。

我不喜欢将您的 int 键包装在另一种类型中的建议。在我看来,简单地添加复杂性以便可以将类型用作 KeyedCollection 中的项目是错误的。我会使用字符串键 (ToString) 而不是这样做 - 这很像 VB6 Collection 类。

FWIW,我前段时间在 MSDN 论坛上问过同样的问题。FxCop 团队的一名成员做出了回应,但没有决定性的指导方针。

于 2008-10-14T17:18:21.630 回答
3

一个简单的解决方案可能是将 包装int到另一种类型中,以创建一个不同的类型来解决重载问题。如果您使用 a struct,则此包装器没有任何额外开销:

struct Id {
    public int Value;

    public Id(int value) { Value = value; }

    override int GetHashCode() { return Value.GetHashCode(); }

    // … Equals method.
}
于 2008-10-14T16:58:01.377 回答
2

最好将GetById(int)方法添加到集合类型。Collection<T>如果您不需要任何其他密钥来访问包含的对象,则可以使用:

public class FooCollection : Collection<Foo>
 { Dictionary<int,Foo> dict = new Dictionary<int,Foo>();

   public Foo GetById(int id) { return dict[id]; }

   public bool Contains(int id) { return  dict.Containskey(id);}

   protected override void InsertItem(Foo f)
    { dict[f.Id] = f;
      base.InsertItem(f);
    }

   protected override void ClearItems()
    { dict.Clear();
      base.ClearItems();
    }

   protected override void RemoveItem(int index)
    { dict.Remove(base.Items[index].Id);
      base.RemoveItem(index);
    }

   protected override void SetItem(int index, Foo item)
    { dict.Remove(base.Items[index].Id);
      dict[item.Id] = item;
      base.SetItem(index, item);
    }
 }









 }
于 2008-10-14T16:52:40.980 回答
1

KeyedCollection 中的键应该是唯一的,并且可以从正在收集的对象中快速导出。例如,给定一个人员类,它可能是 SSN 属性,甚至可能是连接 FirstName 和 LastName 属性(如果已知结果是唯一的)。如果 ID 合法地是正在收集的对象的字段,则它是键的有效候选者。但也许尝试将其转换为字符串以避免碰撞。

于 2008-10-14T16:54:38.747 回答