简体   繁体   English

创建封装Generic Collection的类有缺点吗?

[英]Are there drawbacks to creating a class that encapsulates Generic Collection?

A part of my (C# 3.0 .NET 3.5) application requires several lists of strings to be maintained. 我的(C#3.0 .NET 3.5)应用程序的一部分需要维护几个字符串列表。 I declare them, unsurprisingly, as List<string> and everything works, which is nice. 我毫不奇怪地将它们声明为List<string>并且一切正常,这很好。

The strings in these List s are actually (and always) Fund IDs. 这些List的字符串实际上(并且始终)是基金ID。 I'm wondering if it might be more intention-revealing to be more explicit, eg: 我想知道更明确的意图是否更具意图,例如:

public class FundIdList : List<string> { }

... and this works as well. ......这也有效。 Are there any obvious drawbacks to this, either technically or philosophically? 从技术上还是哲学上来说,这有什么明显的缺点吗?

I would start by going in the other direction: wrapping the string up into a class/struct called FundId. 我将从另一个方向开始:将字符串包装到名为FundId的类/结构中。 The advantage of doing so, I think, is greater than the generic list versus specialised list. 我认为,这样做的优势大于通用列表与专用列表。

  1. You code becomes type-safe: there is a lot less scope for you to pass a string representing something else into a method that expects a fund identifier. 您的代码变得类型安全:将表示其他内容的字符串传递给需要基金标识符的方法的范围要小得多。
  2. You can constrain the strings that are valid in the constructor to FundId, ie enforce a maximum length, check that the code is in the expected format, &c. 您可以将构造函数中有效的字符串约束为FundId,即强制执行最大长度,检查代码是否为预期格式,&c。
  3. You have a place to add methods/functions relating to that type. 您可以添加与该类型相关的方法/功能。 For example, if fund codes starting 'I' are internal funds you could add a property called IsInternal that formalises that. 例如,如果以“I”开头的基金代码是内部基金,您可以添加一个名为IsInternal的属性来形式化。

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 (ie aggregate functions). 至于FundIdList,拥有这样一个类的优势类似于FundId上面的第3点:你有一个地方可以挂钩在FundIds列表上运行的方法/函数(即聚合函数)。 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<> has no virtual or protected members - such classes should almost never be subclassed. List <>没有虚拟或受保护的成员 - 这些类几乎不应该是子类。 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? 此外,虽然你可能需要List<string>的全部功能,但如果你这样做 - 是否有必要制作这样的子类?

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 eg using linq and .ToList since your type is more specific. 如果您将本地类型声明为FundIdList ,那么您将无法通过例如使用linq和.ToList来分配它,因为您的类型更具体。 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 - eg if you demand uniqueness and declare a new Add method, anyone that simply (legally) upcasts to List<string> for instance by passing the list as a parameter typed as such will use the default list Add, not your new Add. 这是有问题的,因为List实现忽略了这些额外的位并且可能违反了你的约束 - 例如,如果你要求唯一性并声明一个新的Add方法,那么任何人(例如)通过将列表作为一个简单地(合法地)向上转换为List<string>任何人这样输入的参数将使用默认列表Add,而不是新的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 a List<FundId> rather than a List<string> . 我更喜欢声明一个包含字符串的struct FundId ,并实现有关该字符串所需的任何保证,然后使用List<FundId>而不是List<string>

Finally, do you really mean List<> ? 最后,你真的是指List<>吗? I see many people use List<> for things for which IEnumerable<> or plain arrays are more suitable. 我看到很多人使用List<>来处理IEnumerable<>或plain数组更适合的事情。 Exposing your internal List in an api is particularly tricky since that means any API user can add/remove/change items. 在api中公开内部List特别棘手,因为这意味着任何API用户都可以添加/删除/更改项目。 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 the List 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. 如果您没有在API中公开List ,而只是将其用于内部簿记,那么声明和使用不添加任何功能的类型(仅文档)并不是那么有趣。

Conclusion 结论

Only use List<> for internals, and don't subclass it if you do. 仅对内部使用List<> ,如果这样做,则不要将其子类化。 If you want some explicit type-safety, wrap string in a struct (not a class, since a struct is more efficient here and has better semantics: there's no confusion between a null FundId and a null string, and object equality and hashcode work as expected with structs but need to be manually specified for classes). 如果你想要一些显式的类型安全,请在结构中包装string (不是类,因为结构在这里更有效并且具有更好的语义:在null的FundId和空字符串之间没有混淆,并且对象相等和哈希码工作为预期结构但需要手动指定类)。 Finally, expose IEnumerable<> if you need to support enumeration, or if you need indexing as well use the simple ReadOnlyCollection<> wrapper around your list rather than let the API client fiddle with internal bits. 最后,如果您需要支持枚举,或者如果您还需要索引,则使用简单的ReadOnlyCollection<>包装器来展示IEnumerable<> ,而不是让API客户端使用内部位。 If you really need a mutatable list API, ObservableCollection<> at least lets you react to changes the client makes. 如果您确实需要可变列表API, ObservableCollection<>至少可以让您对客户端所做的更改做出反应。

Personally I would leave it as a List<string> , or possibly create a FundId class that wraps a string and then store a List<FundId> . 我个人会把它FundId List<string> ,或者可能创建一个包装字符串然后存储List<FundId>FundId类。

The List<FundId> option would enforce type correct-ness and allow you to put some validation on FundIds . List<FundId>选项将强制执行类型更正,并允许您对FundIds进行一些验证。

Just leave it as a List<string> , you variable name is enough to tell others that it's storing FundIDs. 只需将其保留为List<string> ,您的变量名称就足以告诉其他人它正在存储FundID。

var fundIDList = new List<string>();

When do I need to inherit List<T> ? 我什么时候需要继承List<T>

Inherit it if you have really special actions/operations to do to a fund id list. 如果您对基金ID列表有特殊的操作/操作,请继承它。

public class FundIdList : List<string> 
{
   public void SpecialAction()
   {
      //can only do with a fund id list
      //sorry I can't give an example :(
   }
}

Unless I was going to want someone to do everything they could to List<string> , without any intervention on the part of FundIdList I would prefer to implement IList<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 private List<string> when appropriate. 除非我想要某人做他们可以做的所有事情来List<string> ,而没有对FundIdList进行任何干预,我宁愿实现IList<string> (或者如果我不关心的话,可以实现层次结构更高层次的接口大多数接口的成员)并在适当时委托调用私有List<string>

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. 如果我确实希望有人拥有这种程度的控制权,我可能只是首先给他们一个List<string> 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. 据推测,你有一些东西可以确保这些字符串实际上是“基金ID”,当你公开使用继承时,你不能再保证。

Actually, this sounds (and often does with List<T> ) like a natural case for private inheritance. 实际上,这听起来(并且经常使用List<T> )就像私有继承的自然情况一样。 Alas, C# doesn't have private inheritance, so composition is the way to go. 唉,C#没有私有继承,所以组合是要走的路。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM