【问题标题】:IS it OK to use an int for the key in a KeyedCollection可以在 KeyedCollection 中使用 int 作为键吗
【发布时间】:2010-09-17 03:33:03
【问题描述】:

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

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

【问题讨论】:

    标签: c# .net generics collections


    【解决方案1】:

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

    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,以及需要密钥访问的 API 可以使用构造函数定义自己的内部 KeyedCollection,该构造函数采用 IEnumerable 并用它填充集合。这意味着您可以从从 API 检索到的列表轻松构建新的 KeyedCollection。

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

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

    我不喜欢the suggestion of wrapping your int key in another type。在我看来,简单地添加复杂性以便可以将类型用作 KeyedCollection 中的项目是错误的。我会使用字符串键 (ToString) 而不是这样做 - 这很像 VB6 Collection 类。

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

    【讨论】:

    • 我认为我同意不公开在 API 中使用 int 作为密钥的 KeyedCollection。这可能会导致另一端出现错误。序列化问题当然是不序列化集合本身的一个很好的理由,而只是序列化项目(这就是我在我的案例中所做的)。
    • 还有一种方法是使用派生自 KeyedCollection 的类,专门用于整数键,其中包括使用内部 Items 集合获取适当项目的 GetByIndex 或 ItemAt 方法。这使您可以利用现有的 KeyedCollection,而不是下面涉及扩充集合的方法。
    • 你可以通过 threshold = -1 禁用字典
    【解决方案2】:

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

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

    【讨论】:

    • 这是我的最佳答案
    【解决方案3】:

    一个简单的解决方案可能是将int 包装到另一种类型中,以便为重载解析创建一个不同的类型。如果您使用struct,则此包装器不会产生任何额外开销:

    struct Id {
        public int Value;
    
        public Id(int value) { Value = value; }
    
        override int GetHashCode() { return Value.GetHashCode(); }
    
        // … Equals method.
    }
    

    【讨论】:

    • 这是个好主意。然而,使结构不可变也是一个好主意。这样可以避免如下混淆: Id id; id.Value = 2;身份证 id2 = 身份证; id2.Value = 4; // 这之后的 id.Value 是什么?许多人希望它是 4。(抱歉格式化;如果可以的话,我会使用换行符)
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2012-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-07
      • 2013-07-12
      • 2013-12-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多