草庐IT

c# - 为什么不继承List<T>呢?

coder 2023-07-06 原文

在计划我的程序时,我经常从这样的思路开始:

A football team is just a list of football players. Therefore, I should represent it with:

var football_team = new List<FootballPlayer>();

The ordering of this list represent the order in which the players are listed in the roster.

但后来我意识到,除了球员名单之外,球队还有其他必须记录的属性。例如,本赛季的总得分、当前预算、 Jersey 颜色、string。代表队名等。

然后我想:

Okay, a football team is just like a list of players, but additionally, it has a name (a string) and a running total of scores (an int). .NET does not provide a class for storing football teams, so I will make my own class. The most similar and relevant existing structure is List<FootballPlayer>, so I will inherit from it:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

但事实证明a guideline says you shouldn't inherit from List<T> .我在两个方面对这条准则感到非常困惑。

为什么不呢?

显然 List is somehow optimized for performance .怎么会这样?如果我扩展 List 会导致什么性能问题? ?究竟什么会破坏?

我看到的另一个原因是 List由 Microsoft 提供,我无法控制它,所以 I cannot change it later, after exposing a "public API" .但我很难理解这一点。什么是公共(public) API,我为什么要关心?如果我当前的项目没有也不太可能有这个公共(public) API,我可以放心地忽略这个指南吗?如果我继承自 List 并且事实证明我需要一个公共(public)API,我会有什么困难?

为什么这很重要?列表就是列表。什么可能会改变?我可能想要改变什么?

最后,如果 Microsoft 不想让我继承 List ,他们为什么不上课 sealed

我还应该使用什么?

显然,对于自定义集合,微软提供了一个Collection应该扩展而不是 List 的类.但是这个类很简单,没有多少有用的东西,such as AddRange , 例如。 jvitor83's answer提供了该特定方法的性能原理,但是缓慢的 AddRange 是如何实现的?不比没有好 AddRange

继承自 Collection比从 List 继承更多的工作,我看不出有什么好处。微软肯定不会让我无缘无故地做额外的工作,所以我不禁觉得我不知何故误解了一些东西,并继承了Collection实际上不是我的问题的正确解决方案。

我看过一些建议,例如实现 IList .就是不行。这是几十行样板代码,我一无所获。

最后,有人建议包装 List在某事中:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

这有两个问题:

  1. 它使我的代码不必要地冗长。我现在必须调用 my_team.Players.Count而不仅仅是 my_team.Count .值得庆幸的是,使用 C# 我可以定义索引器以使索引透明,并转发内部 List 的所有方法。 ...但是那是很多代码!我做这些工作能得到什么?

  2. 简单明了没有任何意义。足球队没有球员名单。它玩家列表。您不会说“John McFootballer 加入了 SomeTeam 的球员”。你说“约翰加入了 SomeTeam”。您不向“字符串的字符”添加字母,而是向字符串添加字母。您不是将一本书添加到图书馆的图书中,而是将一本书添加到图书馆中。

我意识到“幕后”发生的事情可以说是“将 X 添加到 Y 的内部列表”,但这似乎是一种非常违反直觉的思考世界的方式。

我的问题(总结)

表示数据结构的正确 C# 方法是什么,“逻辑上”(也就是说,“对人的思维”而言)只是一个 listthings有一些花里胡哨的东西?

继承自List<T>总是不能接受?什么时候可以接受?为什么/为什么不?在决定是否从 List<T> 继承时,程序员必须考虑什么还是不是?

最佳答案

这里有一些很好的答案。我会向他们添加以下几点。

What is the correct C# way of representing a data structure, which, "logically" (that is to say, "to the human mind") is just a list of things with a few bells and whistles?

请任意十个熟悉足球存在的非计算机程序员填空:

A football team is a particular kind of _____

有人说“带有一些花里胡哨的足球运动员名单”,还是他们都说“运动队”或“俱乐部”或“组织”?你认为足球队是一种特殊类型的球员的想法存在于你的人类思维中,而且只存在于你的人类思维中。

List<T>是一种机制。足球队是一个业务对象 —— 即表示程序业务领域 中的某些概念的对象。不要混合那些!一支足球队是一种球队;它有一个花名册,一个花名册是一个玩家列表。花名册不是特定类型的球员名单。花名册玩家列表。所以创建一个名为 Roster 的属性那是一个List<Player> .并让它成为ReadOnlyList<Player>除非您相信每个了解足球队的人都可以从花名册中删除球员。

Is inheriting from List<T> always unacceptable?

谁不接受?我?没有。

When is it acceptable?

当您构建扩展 List<T> 的机制时机制

What must a programmer consider, when deciding whether to inherit from List<T> or not?

我是在构建一个机制还是一个业务对象

But that's a lot of code! What do I get for all that work?

您输入问题的时间比为List<T> 的相关成员编写转发方法要花更多时间五十次以上。你显然不怕冗长,我们在这里讨论的代码量非常小;这是几分钟的工作。

更新

我仔细考虑了一下,还有另一个原因不将足球队建模为球员列表。事实上,将足球队建模为也有球员名单可能不是一个好主意。拥有球员名单的球队的问题在于,您得到的是球队某个时刻快照。我不知道你的这个类(class)的商业案例是什么,但如果我有一个代表足球队的类(class),我想问它这样的问题:“2003 年到 2013 年间有多少海鹰队球员因伤缺席比赛?”或者“之前为另一支球队效力的丹佛球员的跑码数同比增幅最大?”或“Did the Piggers go all the way this year?

也就是说,在我看来,足球队可以很好地建模为历史事实的集合,例如球员何时被招募、受伤、退役等。显然,当前球员名单是重要的事实可能应该放在前面和中心,但您可能还想用这个对象做其他有趣的事情,需要更多的历史视角。

关于c# - 为什么不继承List<T>呢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21692193/

有关c# - 为什么不继承List<T>呢?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  3. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  4. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  5. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  6. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

  7. ruby - 当使用::指定模块时,为什么 Ruby 不在更高范围内查找类? - 2

    我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or

  8. ruby - 为什么人们使用 `Module.send(:prepend, …)` ? - 2

    我正在学习如何在我的Ruby代码中使用Module.prepend而不是alias_method_chain,我注意到有些人使用send调用它(example):ActionView::TemplateRenderer.send(:prepend,ActionViewTemplateRendererWithCurrentTemplate)而其他人直接调用它(example):ActionView::TemplateRenderer.prepend(ActionViewTemplateRendererWithCurrentTemplate)而且,虽然我还没有看到任何人使用这种风格,但我从

  9. ruby - 为什么 Ruby 的 each 迭代器先执行? - 2

    我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试

  10. ruby-on-rails - Nokogiri:使用 XPath 搜索 <div> - 2

    我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll

随机推荐