创建封装通用集合的类有缺点吗?
我的(C# 3.0 .NET 3.5)应用程序的一部分需要维护多个字符串列表。毫不奇怪,我将它们声明为 List
并且一切正常,这很好。
这些列表
中的字符串实际上(并且始终)是基金 ID。我想知道是否更明确地更能揭示意图,例如:
public class FundIdList : List<string> { }
……这也有效。无论是在技术上还是在哲学上,这有什么明显的缺点吗?
A part of my (C# 3.0 .NET 3.5) application requires several lists of strings to be maintained. I declare them, unsurprisingly, as List<string>
and everything works, which is nice.
The strings in these List
s are actually (and always) Fund IDs. I'm wondering if it might be more intention-revealing to be more explicit, e.g.:
public class FundIdList : List<string> { }
... and this works as well. Are there any obvious drawbacks to this, either technically or philosophically?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我将从另一个方向开始:将字符串包装到名为 FundId 的类/结构中。我认为,这样做的好处比通用列表与专用列表更大。
至于 FundIdList,拥有这样一个类的优点类似于上面针对 FundId 的第 3 点:您可以挂钩对 FundId 列表进行操作的方法/函数(即聚合函数)。如果没有这样的地方,您会发现静态帮助器方法开始在整个代码中或在某些静态帮助器类中出现。
I would start by going in the other direction: wrapping the string up into a class/struct called FundId. The advantage of doing so, I think, is greater than the generic list versus specialised list.
As for FundIdList, the advantage to having such a class is similar to point 3 above for the FundId: you have a place to hook in methods/functions that operate on the list of FundIds (i.e. aggregate functions). Without such a place, you'll find that static helper methods start to crop up throughout the code or, in some static helper class.
列表>>没有虚拟或受保护的成员 - 此类类几乎永远应该被子类化。另外,虽然您可能需要
List
的完整功能,但如果您这样做 - 创建这样的子类有什么意义吗?子类化有很多缺点。如果您将本地类型声明为
FundIdList
,那么您将无法通过使用 linq 和.ToList
等方式对其进行分配,因为您的类型更加具体。我见过人们决定在此类列表中需要额外的功能,然后将其添加到子类列表类中。这是有问题的,因为 List 实现会忽略此类额外位,并且可能违反您的约束 - 例如,如果您要求唯一性并声明新的 Add 方法,则任何简单(合法)向上转换为List
的人通过将列表作为参数传递的实例,这样键入的将使用默认列表添加,而不是新的添加。您只能添加功能,而不能删除它 - 并且没有需要子类化才能利用的受保护或虚拟成员。因此,您无法真正添加任何使用扩展方法无法添加的功能,并且您的类型不再完全兼容,这限制了您可以对列表执行的操作。
我更喜欢声明一个包含字符串的 struct
FundId
并实现您需要的有关该字符串的任何保证,然后使用List
而不是List< ;字符串>
。最后,您真的是指
List<>
吗? 我看到很多人使用List<>
来处理IEnumerable
的内容。 >
或普通数组更合适。在 API 中公开您的内部List
特别棘手,因为这意味着任何 API 用户都可以添加/删除/更改项目。即使您首先复制列表,这样的返回值仍然具有误导性,因为人们可能期望能够添加/删除/更改项目。如果您没有在 API 中公开List
,而只是将其用于内部簿记,那么声明和使用不添加任何功能的类型就没那么有趣了,仅文档。结论
仅将
List<>
用于内部,如果使用,请勿对其进行子类化。如果您想要一些显式类型安全性,请将string
包装在结构体中(而不是类,因为结构体在这里更高效并且具有更好的语义:空FundId 和一个空字符串,对象相等和哈希码按预期与结构一起工作,但需要为类手动指定)。最后,如果您需要支持枚举,或者如果您还需要索引,请公开
IEnumerable<>
,使用简单的ReadOnlyCollection<>
包装列表,而不是让 API 客户端摆弄内部位。如果您确实需要可变列表 API,ObservableCollection<>
至少可以让您对客户端所做的更改做出反应。List<> has no virtual or protected members - such classes should almost never be subclassed. Also, although it's possible you need the full functionality of
List<string>
, if you do - is there much point to making such a subclass?Subclassing has a variety of downsides. If you declare your local type to be
FundIdList
, then you won't be able to assign to it by e.g. using linq and.ToList
since your type is more specific. I've seen people decide they need extra functionality in such lists, and then add it to the subclassed list class. This is problematic, because the List implementation ignores such extra bits and may violate your constraints - e.g. if you demand uniqueness and declare a new Add method, anyone that simply (legally) upcasts toList<string>
for instance by passing the list as a parameter typed as such will use the default list Add, not your new Add. You can only add functionality, never remove it - and there are no protected or virtual members that require subclassing to exploit.So you can't really add any functionality you couldn't with an extension method, and your types aren't fully compatible anymore which limits what you can do with your list.
I prefer declaring a struct
FundId
containing a string and implementing whatever guarantees concerning that string you need there, and then working with aList<FundId>
rather than aList<string>
.Finally, do you really mean
List<>
? I see many people useList<>
for things for whichIEnumerable<>
or plain arrays are more suitable. Exposing your internalList
in an api is particularly tricky since that means any API user can add/remove/change items. Even if you copy your list first, such a return value is still misleading, since people might expect to be able to add/remove/change items. And if you're not exposing theList
in an API but merely using it for internal bookkeeping, then it's not nearly as interesting to declare and use a type that adds no functionality, only documentation.Conclusion
Only use
List<>
for internals, and don't subclass it if you do. If you want some explicit type-safety, wrapstring
in a struct (not a class, since a struct is more efficient here and has better semantics: there's no confusion between a nullFundId
and a null string, and object equality and hashcode work as expected with structs but need to be manually specified for classes). Finally, exposeIEnumerable<>
if you need to support enumeration, or if you need indexing as well use the simpleReadOnlyCollection<>
wrapper around your list rather than let the API client fiddle with internal bits. If you really need a mutatable list API,ObservableCollection<>
at least lets you react to changes the client makes.就我个人而言,我会将其保留为
List
,或者可能创建一个FundId
类来包装字符串,然后存储List
>。List
选项将强制类型正确性,并允许您对FundIds
进行一些验证。Personally I would leave it as a
List<string>
, or possibly create aFundId
class that wraps a string and then store aList<FundId>
.The
List<FundId>
option would enforce type correct-ness and allow you to put some validation onFundIds
.只需将其保留为
List
,您的变量名称就足以告诉其他人它正在存储 FundID。我什么时候需要继承
List
?如果您确实需要对基金 ID 列表执行特殊操作,请继承它。
Just leave it as a
List<string>
, you variable name is enough to tell others that it's storing FundIDs.When do I need to inherit
List<T>
?Inherit it if you have really special actions/operations to do to a fund id list.
除非我希望有人对
List
尽其所能,而无需FundIdList
进行任何干预,否则我更愿意实现IList
string>
(或者层次结构中更高的接口,如果我不关心该接口的大多数成员),并在适当的时候将调用委托给私有List
。如果我确实希望某人拥有这种程度的控制权,我可能首先会给他们一个
List
。想必您有办法确保这些字符串实际上是“基金 ID”,而当您公开使用继承时,您将无法再保证这一点。实际上,这听起来(对于
List
来说经常如此)就像私有继承的自然情况。唉,C# 没有私有继承,所以组合是最佳选择。Unless I was going to want someone to do everything they could to
List<string>
, without any intervention on the part ofFundIdList
I would prefer to implementIList<string>
(or an interface higher up the hierarchy if I didn't care about most of that interface's members) and delegate calls to a privateList<string>
when appropriate.And if I did want someone to have that degree of control, I'd probably just given them a
List<string>
in the first place. Presumably you have something to make sure such strings actually are "Fund IDs", which you can't guarantee any more when you publicly use inheritance.Actually, this sounds (and often does with
List<T>
) like a natural case for private inheritance. Alas, C# doesn't have private inheritance, so composition is the way to go.