列表/数组/ReadOnlyCollection 创建(和使用)的最佳实践
我的代码中充斥着集合——我想这并不是什么不寻常的事情。然而,各种集合类型的使用并不明显也不简单。一般来说,我想使用公开“最佳”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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
希望理解您的问题后,我认为您必须区分您在班级中创建和管理的内容以及您向外界提供的内容。
在您的班级中,您可以使用最适合您当前任务的任何内容(
List
与Array
与Dictionary
与的优缺点LinkedList 与等)。但这可能与您在公共属性或函数中提供的内容无关。
在你的公共合约(属性和函数)中,你应该返回所需的最少类型(甚至更好的接口)。所以只是一些公共类型的
IList
、ICollection
、IDictionary
、IEnumerable
。数千导致您的消费者类只是等待接口而不是具体类,因此您可以在稍后阶段更改具体实现,而不会破坏您的公共合同(由于性能原因,请使用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 anList<>
instead of aLinkedList<>
or vice versa).更新:
所以,严格来说这并不是新;但这个问题说服我 继续宣布 开源我已经进行了一段时间的项目(仍在进行中,但其中有一些有用的东西),其中包括一个
IArray
接口(和实现,自然地)我认为这正是您想要的:一个索引、只读、甚至协变(奖励!)界面。一些好处:
ReadOnlyCollection
这样的具体类型,因此它不会将您束缚于特定的实现。ReadOnlyCollection
),因此它“确实是”只读的。ArrayBase
(也在库中)派生并覆盖this[int]
和Count< /code> 属性,就完成了。
如果这听起来很有希望,请随时检查一下并告诉我你的想法。
我不是 100% 清楚哪里您担心这种“语法噪音”:在您的代码中还是在调用代码中?
如果您可以容忍自己的封装代码中的一些“噪音”,那么我建议包装一个
T[]
数组并公开一个IList
,它恰好是aReadOnlyCollection
:是的,您这边有一些噪音,但这还不错。而且你暴露的界面非常干净。
另一种选择是制作自己的界面,类似
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:
ReadOnlyCollection<T>
, so it doesn't tie you down to a specific implementation.ReadOnlyCollection<T>
), so it "really is" read-only.ArrayBase<T>
(also in the library) and override thethis[int]
andCount
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 anIList<T>
which happens to be aReadOnlyCollection<T>
: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 aT[]
and provides a get-only indexer. Then expose that. This is basically as clean as exposing aT[]
but without falsely conveying the idea that items can be set by index.如果我能帮忙的话,我不会传递
Lists
。一般来说,我还有其他东西来管理有问题的集合,它公开了集合,例如:所以这将是传递的对象:
我喜欢这种方法,因为:
1)我可以在构造函数中填充我的值。当任何人
新
拥有SomeCollection
时,它们就在那里。2) 如果我愿意,我可以限制对底层列表的访问。在我的示例中,我将其全部公开,但您不必这样做。如果需要,您可以将其设置为只读,或者在添加之前验证列表中的添加内容。
3)很干净。在任何地方,
SomeCollection
都比List
更容易阅读。4)如果你突然意识到你选择的集合效率低下,你可以更改底层集合类型,而不必去更改它作为参数传递的所有位置(你能想象你可能遇到的麻烦吗,比如说,
列表
?)I do not pass around
Lists
s if I can possibly help it. Generally I have something else that is managing the collection in question, which exposes the collection, for example:And so this would be the object passed around:
I like this approach, because:
1) I can populate my values in the ctor. They're there the momeny anyone
new
sSomeCollection
.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
thanList<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>
?)我同意。 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.
Microsoft 创建了集合指南 这份文件内容丰富,列出了该做和不该做的事情,可以解决您的大部分问题。
这是一个很长的列表,所以这里是最相关的:
正如最后一点所述,您不应该像您建议的那样避免 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:
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.