列表/数组/ReadOnlyCollection 创建(和使用)的最佳实践

发布于 2024-10-11 00:23:56 字数 1455 浏览 3 评论 0原文

我的代码中充斥着集合——我想这并不是什么不寻常的事情。然而,各种集合类型的使用并不明显也不简单。一般来说,我想使用公开“最佳”API 且语法噪音最少的类型。 (请参阅返回值数组时的最佳实践针对类似问题使用列表数组 - 最佳实践)。有一些指南建议在 API 中使用什么类型,但这些指南在普通(非 API)代码中是不切实际的。

例如:

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

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

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

那么,处理制作小集合来传递值的标准任务的最佳实践方法是什么? 在可能的情况下,数组应该优先于 List 吗?完全是别的东西吗?传递如此小的集合的最佳方式是什么——干净、可读、相当有效?特别是,代码对于未来的维护者来说应该是显而易见的,他们还没有阅读过这个问题,并且不想阅读大量的 API 文档,但仍然了解其意图是什么。 最大限度地减少代码混乱也非常重要 - 因此像 ReadOnlyCollection 这样的东西充其量是值得怀疑的。对于具有较小表面的主要 API 而言,冗长的类型没有什么问题,但作为大型代码库中的一般做法则不然。

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

编辑:澄清这是为了制作简短、清晰的代码,而不是公共 API。

My code is littered with collections - not an unusual thing, I suppose. However, usage of the various collection types isn't obvious nor trivial. Generally, I'd like to use the type that's exposes the "best" API, and has the least syntactic noise. (See Best practice when returning an array of values, Using list arrays - Best practices for comparable questions). There are guidelines suggesting what types to use in an API, but these are impractical in normal (non-API) code.

For instance:

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

List's are a very common datastructure, but creating them in this fashion involves quite a bit of syntactic noise - and it can easily get even worse (e.g. dictionaries). As it turns out, many lists are never changed, or at least never extended. Of course ReadOnlyCollection introduces yet more syntactic noise, and it doesn't even convey quite what I mean; after all ReadOnlyCollection may wrap a mutating collection. Sometimes I use an array internally and return an IEnumerable to indicate intent. But most of these approaches have a very low signal-to-noise ratio; and that's absolutely critical to understanding code.

For the 99% of all code that is not a public API, it's not necessary to follow Framework Guidelines: however, I still want a comprehensible code and a type that communicates intent.

So, what's the best-practice way to deal with the bog-standard task of making small collections to pass around values? Should array be preferred over List where possible? Something else entirely? What's the best way - clean, readable, reasonably efficient - of passing around such small collections? In particular, code should be obvious to future maintainers that have not read this question and don't want to read swathes of API docs yet still understand what the intent is. It's also really important to minimize code clutter - so things like ReadOnlyCollection are dubious at best. Nothing wrong with wordy types for major API's with small surfaces, but not as a general practice inside a large codebase.

What's the best way to pass around lists of values without lots of code clutter (such as explicit type parameters) but that still communicates intent clearly?

Edit: clarified that this is about making short, clear code, not about public API's.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

临走之时 2024-10-18 00:23:56

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

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

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

After hopefully understanding your question, i think you have to distinguish between what you create and manage within your class and what you make available to the outside world.

Within your class you can use whatever best fits your current task (pro/cons of List vs. Array vs. Dictionary vs. LinkedList vs. etc.). But this has maybe nothing to do about what you provide in your public properties or functions.

Within your public contract (properties and functions) you should give back the least type (or even better interface) that is needed. So just an IList, ICollection, IDictionary, IEnumerable of some public type. Thous leads that your consumer classes are just awaiting interfaces instead of concrete classes and so you can change the concrete implementation at a later stage without breaking your public contract (due to performance reasons use an List<> instead of a LinkedList<> or vice versa).

混吃等死 2024-10-18 00:23:56

更新

所以,严格来说这并不是;但这个问题说服我 继续宣布 开源我已经进行了一段时间的项目(仍在进行中,但其中有一些有用的东西),其中包括一个 IArray 接口(和实现,自然地)我认为这正是您想要的:一个索引、只读、甚至协变(奖励!)界面

一些好处:

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

如果这听起来很有希望,请随时检查一下并告诉我你的想法。


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

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

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

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

另一种选择是制作自己的界面,类似IArray< T>,它包装了 T[] 并提供了仅获取索引器。然后曝光这一点。这基本上与公开 T[] 一样干净,但不会错误地传达项目可以通过索引设置的想法。

Update:

So, this isn't strictly speaking new; but this question convinced me to go ahead and announce an open source project I've had in the works for a while (still a work in progress, but there's some useful stuff in there), which includes an IArray<T> interface (and implementations, naturally) that I think captures exactly what you want here: an indexed, read-only, even covariant (bonus!) interface.

Some benefits:

  • It's not a concrete type like ReadOnlyCollection<T>, so it doesn't tie you down to a specific implementation.
  • It's not just a wrapper (like ReadOnlyCollection<T>), so it "really is" read-only.
  • It clears the way for some really nice extension methods. So far the Tao.NET library only has two (I know, weak), but more are on the way. And you can easily make your own, too—just derive from ArrayBase<T> (also in the library) and override the this[int] and Count properties and you're done.

If this sounds promising to you, feel free to check it out and let me know what you think.


It's not 100% clear to me where you're worried about this "syntactic noise": in your code or in calling code?

If you're tolerant of some "noise" in your own encapsulated code then I would suggest wrapping a T[] array and exposing an IList<T> which happens to be a ReadOnlyCollection<T>:

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

Yes there is some noise on your end, but it's not bad. And the interface you expose is quite clean.

Another option is to make your own interface, something like IArray<T>, which wraps a T[] and provides a get-only indexer. Then expose that. This is basically as clean as exposing a T[] but without falsely conveying the idea that items can be set by index.

滥情哥ㄟ 2024-10-18 00:23:56

如果我能帮忙的话,我不会传递Lists。一般来说,我还有其他东西来管理有问题的集合,它公开了集合,例如:

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)我可以在构造函数中填充我的值。当任何人拥有SomeCollection时,它们就在那里。

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

3)很干净。在任何地方,SomeCollection 都比 List 更容易阅读。

4)如果你突然意识到你选择的集合效率低下,你可以更改底层集合类型,而不必去更改它作为参数传递的所有位置(你能想象你可能遇到的麻烦吗,比如说, 列表?)

I do not pass around Listss if I can possibly help it. Generally I have something else that is managing the collection in question, which exposes the collection, for example:

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

And so this would be the object passed around:

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

I like this approach, because:

1) I can populate my values in the ctor. They're there the momeny anyone news SomeCollection.

2) I can restrict access, if I want, to the underlying list. In my example I exposed it all, but you don't have to do this. You can make it read-only if you want, or validate additions to the list, prior to adding them.

3) It's clean. Far easier to read SomeCollection than List<SomeObject> everywhere.

4) If you suddenly realise that your collection of choice is inefficient, you can change the underlying collection type without having to go and change all the places where it got passed as a parameter (can you imagine the trouble you might have with, say, List<String>?)

め七分饶幸 2024-10-18 00:23:56

我同意。 IList 与只读集合和可修改集合的结合过于紧密。 IList 应该继承自 IReadOnlyList。

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

1.

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

如果您稍后希望允许像 IReadOnlyCollection 那样修改 ReadOnly 包装器,您可以创建另一个类,它是自定义 ReadOnly Collection 的包装器,并实现了 Insert/Add/Remove/RemoveAt/Clear/... 并缓存这些更改。

2.

使用 ObservableCollection/ListViewCollection 并制作自己的自定义 ReadOnlyObservableCollection 包装器,如 #1 中所示,不实现添加或修改属性和方法。

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

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

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

I agree. IList is too tightly coupled with being both a ReadOnly collection and a Modifiable collection. IList should have inherited from an IReadOnlyList.

Casting back to IReadOnlyList wouldn't require a explicit cast. Casting forward would.

1.

Define your own class which implements IEnumerator, takes an IList in the new constructor, has a read only default item property taking an index, and does not include any properties/methods that could otherwise allow your list to me manipulated.

If you later want to allow modifying the ReadOnly wrapper like IReadOnlyCollection does, you can make another class which is a wrapper around your custom ReadOnly Collection and has the Insert/Add/Remove/RemoveAt/Clear/...implemented and cache those changes.

2.

Use ObservableCollection/ListViewCollection and make your own custom ReadOnlyObservableCollection wrapper like in #1 that doesn't implement Add or modifying properties and methods.

ObservableCollection can bind to ListViewCollection in such a way that changes to ListViewCollection do not get pushed back into ObservableCollection. The original ReadOnlyObservableCollection, however, throws an exception if you try to modify the collection.

If you need backwards/forwards compatibility, make two new classes inheriting from these. Then Implement IBindingList and handle/translate CollectionChanged Event (INotifyCollectionChanged event) to the appropriate IBindingList events.

Then you can bind it to older DataGridView and WinForm controls, as well as WPF/Silverlight controls.

猫九 2024-10-18 00:23:56

Microsoft 创建了集合指南 这份文件内容丰富,列出了该做和不该做的事情,可以解决您的大部分问题。

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

比起数组,更喜欢集合。

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

请勿在公共 API 中使用哈希表或字典。

请勿在公共 API 中使用弱类型集合。

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

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

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

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

Microsoft has created a Guidelines for Collections document which is a very informative list of DOs and DON'Ts that address most of your question.

It's a long list so here are the most relevant ones:

DO prefer collections over arrays.

DO NOT use ArrayList or List in public APIs. (public properties, public parameters and return types of public methods)

DO NOT use Hashtable or Dictionary in public APIs.

DO NOT use weakly typed collections in public APIs.

DO use the least-specialized type possible as a parameter type. Most members taking collections as parameters use the IEnumerable interface.

AVOID using ICollection or ICollection as a parameter just to access the Count property.

DO use ReadOnlyCollection, a subclass of ReadOnlyCollection, or in rare cases IEnumerable for properties or return values representing read-only collections.

As the last point states, you shouldn't avoid ReadOnlyCollection like you were suggesting. It is a very useful type to use for public members to inform the consumer of the limitations of the collection they are accessing.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文