【问题标题】:Best Practice List/Array/ReadOnlyCollection creation (and usage)最佳实践列表/数组/ReadOnlyCollection 创建(和使用)
【发布时间】:2011-01-06 12:10:22
【问题描述】:

我的代码中到处都是集合——我想这不是什么不寻常的事情。然而,各种集合类型的使用并不明显也不是微不足道的。一般来说,我喜欢使用公开“最佳”API 并且语法噪音最小的类型。 (有关可比较的问题,请参阅Best practice when returning an array of valuesUsing list arrays - Best practices)。有一些指南建议在 API 中使用哪些类型,但这些在普通(非 API)代码中是不切实际的。

例如:

new ReadOnlyCollection<Tuple<string,int>>(
    new List<Tuple<string,int>> {
        Tuple.Create("abc",3),
        Tuple.Create("def",37)
    }
)

List's 是一种非常常见的数据结构,但以这种方式创建它们会产生相当多的语法噪音 - 而且它很容易变得更糟(例如字典)。事实证明,许多列表从未更改,或者至少从未扩展。当然ReadOnlyCollection 引入了更多的语法噪音,它甚至没有完全传达我的意思;毕竟ReadOnlyCollection 可以包装一个 mutating 集合。有时我在内部使用一个数组并返回一个IEnumerable 来表示意图。但是这些方法中的大多数都具有非常低的信噪比;这对于理解代码绝对至关重要。

对于所有 不是公共 API 的代码,没有必要遵循框架指南:但是,我仍然想要一个可理解的代码和一个类型传达意图。

那么,处理制作小型集合以传递值的沼泽标准任务的最佳实践方法是什么? 是否应该尽可能首选数组而不是 List?完全不同的东西?传递如此小的集合的最佳方式是什么——干净、可读、相当有效?特别是,对于未来的维护者代码应该是显而易见的 并且不想阅读大量 API 文档但仍了解其意图是什么。 尽量减少代码混乱也很重要 - 所以像ReadOnlyCollection 这样的东西充其量是可疑的。对于具有小表面的主要 API 来说,冗长的类型没有错,但在大型代码库中却不是一般做法。

在没有大量代码混乱(例如显式类型参数)的情况下传递值列表但仍能清楚地传达意图的最佳方式是什么?

编辑:澄清说这是为了编写简短、清晰的代码,而不是关于公共 API。

【问题讨论】:

  • 所以,我实际上得到了an open source library,其中包括一个IArray&lt;T&gt; 接口,我认为该接口非常适合此目的。有关更多信息,请参阅我的更新答案。

标签: c# .net generics immutability


【解决方案1】:

希望在理解您的问题之后,我认为您必须区分您在课堂上创建和管理的内容以及您向外界提供的内容。

在您的班级中,您可以使用最适合您当前任务的任何内容(List vs. Array vs. Dictionary vs. LinkedList vs. 等)。但这可能与您在公共属性或函数中提供的内容无关。

在您的公共合约(属性和函数)中,您应该返回所需的最少类型(甚至更好的接口)。所以只是一些公共类型的IListICollectionIDictionaryIEnumerable。 Thou 导致您的消费者类只是在等待接口而不是具体类,因此您可以在稍后阶段更改具体实现而不会破坏您的公共合同(由于性能原因,请使用 List&lt;&gt; 而不是 LinkedList&lt;&gt; 或反之亦然)。

【讨论】:

  • 也许我需要澄清我的问题:我特别关注避免代码膨胀和交流意图。传递 IList/IDictionaries 通常不好,因为我无法沟通也无法强制执行这些视图的只读性质(例如,IList 比此处的数组更糟糕)
  • 第二点,我希望生产者和消费者是可读的——所以我想尽量减少调用,并尽可能避免所有显式模板参数。
  • @Eamon:虽然我明白你的意思,但从技术上讲,IList&lt;T&gt; 能够表达意图,因为它具有IsReadOnly 属性。不过,我承认 没有人 看过这个。但是,在 BCL 中有它的先例(不管它值多少钱);例如,考虑SortedList&lt;TKey, TValue&gt;.Keys 属性。只读,公开为IList&lt;TKey&gt;
  • 是的,在修改代码(并因此进行静态推理)时,IsReadOnly 无论如何都没有多大帮助 - 你想知道而不是测试 列表是否可以更新。
【解决方案2】:

更新

所以,这并不是严格意义上的;但是这个问题说服了我 go ahead and announce an open source project I've had in the works for a while (仍在进行中,但里面有一些有用的东西),其中包括一个 IArray&lt;T&gt; 接口(当然还有实现),我认为它准确地捕捉到了你想要的东西: 一个索引的、只读的、甚至 协变的(奖励!)接口

一些好处:

  • 它不是像 ReadOnlyCollection&lt;T&gt; 这样的具体类型,因此它不会将您束缚于特定的实现。
  • 它不仅仅是一个包装器(如ReadOnlyCollection&lt;T&gt;),因此它“确实”是只读的。
  • 它为一些非常好的扩展方法扫清了道路。到目前为止,Tao.NET 库只有两个(我知道,很弱),但更多的正在开发中。您也可以轻松创建自己的 - 只需从 ArrayBase&lt;T&gt;(也在库中)派生并覆盖 this[int]Count 属性即可。

如果您觉得这很有希望,请随时check it out 告诉我您的想法。


我不是 100% 清楚在哪里你担心这种“句法噪音”:在 你的代码中或在调用代码中?

如果您可以容忍自己封装的代码中的一些“噪音”,那么我建议包装一个 T[] 数组并公开一个 IList&lt;T&gt;,它恰好是一个 ReadOnlyCollection&lt;T&gt;

class ThingsCollection
{
    ReadOnlyCollection<Thing> _things;

    public ThingsCollection()
    {
        Thing[] things = CreateThings();
        _things = Array.AsReadOnly(things);
    }

    public IList<Thing> Things
    {
        get { return _things; }
    }

    protected virtual Thing[] CreateThings()
    {
        // Whatever you want, obviously.
        return new Thing[0];
    }
}

是的,您这边有一些噪音,但还不错。而且你暴露的界面很干净。

另一种选择是创建自己的接口something like IArray&lt;T&gt;,它包装了T[],并提供了一个仅获取索引器。那就揭穿吧。这基本上与公开T[] 一样干净,但不会错误地传达可以通过索引设置项目的想法。

【讨论】:

  • 我想避免被调用者和调用者的噪音。没有必要将 ReadOnlyCollection 公开为除自身之外的任何内容:毕竟,将其公开为 IList 只会让人感到困惑(我从哪里得到 this IList - 我可以修改它吗?),以及任何IList 可作为 ReadOnlyCollection 公开,因此不失一般性。
  • 感谢IArray讨论的精彩链接!
  • @Eamon:我之所以建议IList&lt;T&gt; 而不是ReadOnlyCollection&lt;T&gt;,是因为我认为您担心名称本身的冗长。但是,是的,我不得不同意 BCL 中肯定有一个“洞”,只读索引集合接口应该去的地方。 IList&lt;T&gt; 只是多用途。
  • 转换为 Ilist 允许修改基础集合。 ReadOnlyCollection 是一个更好的解决方法(因为他们没有 IList 实现某种 IReadOnlyList)。
【解决方案3】:

如果我能帮上忙,我不会绕过Listss。一般来说,我有其他东西正在管理相关集合,它会公开集合,例如:

public class SomeCollection
{
    private List<SomeObject> m_Objects = new List<SomeObject>();

    // ctor
    public SomeCollection()
    {
        // Initialise list here, or wot-not/
    } // eo ctor


    public List<SomeObject> Objects { get { return m_Objects; } }
} // eo class SomeCollection

所以这将是传递的对象:

public void SomeFunction(SomeCollection _collection)
{
    // work with _collection.Objects
} // eo SomeFunction

我喜欢这种方法,因为:

1) 我可以在 ctor 中填充我的值。他们在那里有任何人news SomeCollection

2) 如果需要,我可以限制对基础列表的访问。在我的示例中,我将其全部公开,但您不必这样做。如果需要,您可以将其设为只读,或在添加之前验证添加到列表中的内容。

3) 很干净。在任何地方阅读SomeCollection 都比阅读List&lt;SomeObject&gt; 容易得多。

4) 如果你突然意识到你选择的集合效率低下,你可以改变底层集合类型,而不必去改变它作为参数传递的所有地方(你能想象你可能遇到的麻烦吗? ,比如说List&lt;String&gt;?)

【讨论】:

  • 我想这是一种巧妙的分离;但它使消费者更加冗长(需要到处使用 .Objects),并且使构造更加冗长(不能使用 .ToList 或集合初始化程序)。而且它也没有真正传达意图:这样的列表表明您可以添加/删除值,或更改它们:但这根本不是典型的用法。
【解决方案4】:

我同意。 IList 与 ReadOnly 集合和 Modifiable 集合的耦合过于紧密。 IList 应该继承自 IReadOnlyList。

转换回 IReadOnlyList 不需要显式转换。向前投射会。

1.

定义您自己的实现 IEnumerator 的类,在新的构造函数中采用 IList,具有采用索引的只读默认项属性,并且不包括任何可能允许我操纵您的列表的属性/方法。

如果您以后想要像 IReadOnlyCollection 那样修改 ReadOnly 包装器,您可以创建另一个类,它是您的自定义 ReadOnly 集合的包装器,并具有 Insert/Add/Remove/RemoveAt/Clear/... 实现和缓存这些变化。

2.

使用 ObservableCollection/ListViewCollection 并制作您自己的自定义 ReadOnlyObservableCollection 包装器,就像在 #1 中那样不实现添加或修改属性和方法。

ObservableCollection 可以绑定到 ListViewCollection,这样对 ListViewCollection 的更改不会被推回 ObservableCollection。但是,如果您尝试修改集合,原始的 ReadOnlyObservableCollection 会引发异常。

如果您需要向后/向前兼容性,请创建两个继承自这些的新类。然后实现 IBindingList 并将 CollectionChanged 事件(INotifyCollectionChanged 事件)处理/翻译为相应的 IBindingList 事件。

然后你可以将它绑定到旧的 DataGridView 和 WinForm 控件,以及 WPF/Silverlight 控件。

【讨论】:

    【解决方案5】:

    Microsoft 已创建了一个 Guidelines for Collections 文档,该文档是一个非常有用的 DO 和 DON'T 列表,可以解决您的大部分问题。

    这是一个很长的列表,所以这里是最相关的:

    比起数组更喜欢集合。

    不要在公共 API 中使用 ArrayList 或 List。 (公共属性、公共参数和公共方法的返回类型)

    不要在公共 API 中使用 Hashtable 或 Dictionary。

    不要在公共 API 中使用弱类型集合。

    请尽可能使用最不专业的类型作为参数类型。大多数将集合作为参数的成员都使用 IEnumerable 接口。

    避免使用 ICollection 或 ICollection 作为参数来访问 Count 属性。

    请务必使用 ReadOnlyCollection(ReadOnlyCollection 的子类),或者在极少数情况下使用 IEnumerable 作为表示只读集合的​​属性或返回值。

    正如最后一点所述,您不应该像您建议的那样避免使用 ReadOnlyCollection。它是一种非常有用的类型,可用于公共成员告知消费者他们正在访问的集合的限制。

    【讨论】:

      猜你喜欢
      • 2012-03-29
      • 1970-01-01
      • 2018-03-17
      • 2012-11-16
      • 2020-03-21
      • 1970-01-01
      • 1970-01-01
      • 2012-05-24
      • 2015-08-21
      相关资源
      最近更新 更多