c# tutorial 為什麼不從List<T>繼承?




c# inheritance tutorial (20)

在規劃我的節目時,我經常從一系列的想法開始,比如:

足球隊只是一個足球運動員的名單。 因此,我應該用:

var football_team = new List<FootballPlayer>();

此列表的排序表示球員列入名單的順序。

但我後來認識到,除了僅僅是球員名單之外,球隊還有其他屬性,必須記錄下來。 例如,本賽季得分總數,當前預算,統一顏色,代表球隊名稱的string等。

那麼我想:

好吧,足球隊就像一個球員列表,但另外它有一個名字(一個string )和一個總分數(一個int )。 .NET不提供存儲橄欖球隊的類,所以我會自己創建類。 最相似和相關的現有結構是List<FootballPlayer> ,所以我將繼承它:

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

但事實證明, 指南指出你不應該繼承List<T> 。 本指南在兩方面讓我感到十分困惑。

為什麼不?

顯然, List以某種方式優化了性能 。 怎麼會這樣? 如果我擴展List會導致哪些性能問題? 什麼會打破?

我看到的另一個原因是List是由Microsoft提供的,我無法控制它,因此在公開“公共API”後我不能在以後更改它 。 但我很難理解這一點。 什麼是公共API,我為什麼要關心? 如果我目前的項目沒有,也不可能擁有這個公共API,我可以放心地忽略這個指南嗎? 如果我從List繼承,事實證明我需要一個公共API,那麼我會遇到什麼困難?

為什麼它甚至重要? 列表是一個列表。 什麼可能改變? 我可能想要改變什麼?

最後,如果微軟不希望我繼承List ,為什麼他們沒有把課程sealed

我還應該使用什麼?

顯然,對於自定義集合,Microsoft提供了一個應該擴展而不是ListCollection類。 但是這個類是非常AddRange ,並沒有很多有用的東西, 比如AddRange 。 jvitor83的答案提供了該特定方法的性能基本原理,但是如何緩慢的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的內部列表中添加X”,但這似乎是一種非常直觀的思考世界的方式。

我的問題(總結)

什麼是正確的C#表示數據結構的方式,“邏輯地”(即,“對人類的頭腦”)只是thingslist和幾個花里胡哨的?

List<T>繼承始終是不可接受的? 什麼時候可以接受? 為什麼/為什麼不? 在決定是否繼承List<T>時,程序員必須考慮什麼?


I think I don't agree with your generalization. A team isn't just a collection of players. A team has so much more information about it - name, emblem, collection of management/admin staff, collection of coaching crew, then collection of players. So properly, your FootballTeam class should have 3 collections and not itself be a collection; if it is to properly model the real world.

You could consider a PlayerCollection class which like the Specialized StringCollection offers some other facilities - like validation and checks before objects are added to or removed from the internal store.

Perhaps, the notion of a PlayerCollection betters suits your preferred approach?

public class PlayerCollection : Collection<Player> 
{ 
}

And then the FootballTeam can look like this:

public class FootballTeam 
{ 
    public string Name { get; set; }
    public string Location { get; set; }

    public ManagementCollection Management { get; protected set; } = new ManagementCollection();

    public CoachingCollection CoachingCrew { get; protected set; } = new CoachingCollection();

    public PlayerCollection Players { get; protected set; } = new PlayerCollection();
}

There are a lot excellent answers here, but I want to touch on something I didn't see mentioned: Object oriented design is about empowering objects .

You want to encapsulate all your rules, additional work and internal details inside an appropriate object. In this way other objects interacting with this one don't have to worry about it all. In fact, you want to go a step further and actively prevent other objects from bypassing these internals.

When you inherit from List , all other objects can see you as a List. They have direct access to the methods for adding and removing players. And you'll have lost your control; 例如:

Suppose you want to differentiate when a player leaves by knowing whether they retired, resigned or were fired. You could implement a RemovePlayer method that takes an appropriate input enum. However, by inheriting from List , you would be unable to prevent direct access to Remove , RemoveAll and even Clear . As a result, you've actually disempowered your FootballTeam class.

Additional thoughts on encapsulation... You raised the following concern:

It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count.

You're correct, that would be needlessly verbose for all clients to use you team. However, that problem is very small in comparison to the fact that you've exposed List Players to all and sundry so they can fiddle with your team without your consent.

You go on to say:

It just plain doesn't make any sense. A football team doesn't "have" a list of players. It is the list of players. You don't say "John McFootballer has joined SomeTeam's players". You say "John has joined SomeTeam".

You're wrong about the first bit: Drop the word 'list', and it's actually obvious that a team does have players.
However, you hit the nail on the head with the second. You don't want clients calling ateam.Players.Add(...) . You do want them calling ateam.AddPlayer(...) . And your implemention would (possibly amongst other things) call Players.Add(...) internally.

Hopefully you can see how important encapsulation is to the objective of empowering your objects. You want to allow each class to do its job well without fear of interference from other objects.


哇,你的文章有很多問題和觀點。 你從微軟獲得的大部分推理都是正確的。 讓我們從關於List<T>一切開始

  • List<T>高度優化。 它的主要用途是用作對象的私有成員。
  • 微軟並沒有給它class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... } ,因為有時你可能想創建一個具有友好名稱的class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... } 。 現在就像做var list = new MyList<int, string>();
  • CA1002:不要公開通用名單 :基本上,即使你打算使用這個應用程序作為唯一的開發者,也應該用良好的編碼實踐進行開發,這樣他們才能灌輸到你的身上和第二性質。 如果您需要任何消費者擁有索引列表,您仍然可以將列表公開為IList<T> 。 這讓你在稍後改變類的實現。
  • 微軟使Collection<T>非常通用,因為它是一個通用的概念......名字說明了一切; 它只是一個集合。 有更精確的版本,如SortedCollection<T>ObservableCollection<T>ReadOnlyCollection<T>等,每個實現IList<T>不是 List<T>
  • Collection<T>允許成員(即添加,刪除等)被覆蓋,因為它們是虛擬的。 List<T>不。
  • 你問題的最後部分是現貨。 足球隊不僅僅是一個球員列表,所以它應該是一個包含球員列表的類。 構思與繼承 。 足球隊一個球員名單(名冊),這不是一個球員名單。

如果我寫這段代碼,這個類可能看起來像這樣:

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    // Yes. I used LINQ here. This is so I don't have to worry about
    // _roster.Length vs _roster.Count vs anything else.
    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

這是compositioninheritance的典型例子。

在這個特定的情況下:

球隊是否有增加行為的球員名單?

要么

球隊是否屬於自己的對象,恰好包含了球員列表。

通過擴展List,您可以通過多種方式來限制自己:

  1. 您不能限制訪問權限(例如,阻止人員更改名單)。 無論您是否需要/需要它們,您都可以獲得所有List方法。

  2. 如果您想要列出其他內容,會發生什麼情況。 例如,球隊有教練,經理,球迷,裝備等。其中一些很可能是他們自己的名單。

  3. 你限制你的繼承選項。 例如,您可能想要創建一個通用的Team對象,然後讓BaseballTeam,FootballTeam等從中繼承。 要從List繼承,您需要從Team繼承,但這意味著所有不同類型的團隊都被迫擁有與該名單相同的實現。

構圖 - 包括一個對象,在對像中給出你想要的行為。

繼承 - 您的對象成為具有所需行為的對象的實例。

兩者都有其用途,但這是一個明顯的情況,其中組合物是優選的。


A football team is not a list of football players. A football team is composed of a list of football players!

This is logically wrong:

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

and this is correct:

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

這裡有一些很好的答案。 我會給他們添加以下幾點。

什麼是正確的C#表示數據結構的方式,“邏輯地”(即,“對人類的頭腦”)只是一些事情的列表和幾個花里胡哨的?

請任何十位熟悉足球存在的非計算機程序員的人填寫空白處:

A football team is a particular kind of _____

有沒有說過“有幾個足球運動員名單”,還是他們都說“運動隊”,“俱樂部”或“組織”? 你的觀點,即足球隊是一種特殊的球員列表,只有你的人類思想和人類的思想。

List<T>是一個機制 。 足球隊是一個業務對象 - 也就是說,代表某個概念的對像在程序的業務領域 。 不要混合這些! 足球隊是一種團隊; 它有一個名冊,一個名冊是一個球員名單 。 名冊不是一種特殊的球員列表 。 名單一個球員名單。 因此,創建一個名為Roster的屬性是一個List<Player> 。 並且在你看到它的時候使它成為ReadOnlyList<Player> ,除非你相信每個了解足球隊的人都會從名單中刪除球員。

List<T>繼承始終是不可接受的?

誰不能接受? 我? 沒有。

什麼時候可以接受?

在構建擴展List<T>機制的機制時

在決定是否繼承List<T>時,程序員必須考慮什麼?

我是在建立一個機制還是一個商業對象

但是這是很多代碼! 我為這些工作得到什麼?

你花了更多的時間來輸入你的問題,它會讓你為List<T>的相關成員編寫50次轉發方法。 你顯然不怕冗長,而且我們在這裡談論的是非常少量的代碼; 這是幾分鐘的工作。

UPDATE

我給了它更多的思考,還有另一個理由不模仿一個足球隊作為球員名單。 事實上,將一個橄欖球隊建模成球員名單也許是一個不好的主意。 球隊/球員名單的問題在於,你所得到的是球隊在某個時刻快照 。 我不知道你們這個班的商業案例是什麼,但是如果我有一個代表足球隊的課程,我想問一些問題,比如“在2003年到2013年期間有多少海鷹隊球員因傷缺席比賽?” 或者“之前曾效力於另一支球隊的丹佛球員的年度同比漲幅最大嗎?” 或者“ 這些豬人今年一路走來了嗎?

也就是說,足球隊在我看來很好的模仿了一系列歷史事實,例如當一名球員被招募,受傷,退役等等。顯然,當前的球員名單是一個重要的事實,中心,但可能還有其他有趣的事情需要用更多的歷史視角來處理。


It depends on the context

When you consider your team as a list of players, you are projecting the "idea" of a foot ball team down to one aspect: You reduce the "team" to the people you see on the field. This projection is only correct in a certain context. In a different context, this might be completely wrong. Imagine you want to become a sponsor of the team. So you have to talk to the managers of the team. In this context the team is projected to the list of its managers. And these two lists usually don't overlap very much. Other contexts are the current versus the former players, etc.

Unclear semantics

So the problem with considering a team as a list of its players is that its semantic depends on the context and that it cannot be extended when the context changes. Additionally it is hard to express, which context you are using.

Classes are extensible

When you using a class with only one member (eg IList activePlayers ), you can use the name of the member (and additionally its comment) to make the context clear. When there are additional contexts, you just add an additional member.

Classes are more complex

In some cases it might be overkill to create a extra class. Each class definition must be loaded through the classloader and will be cached by the virtual machine. This costs you runtime performance and memory. When you have a very specific context it might be OK to consider a football team as a list of players. But in this case, you should really just use a IList , not a class derived from it.

Conclusion / Considerations

When you have a very specific context, it is OK to consider a team as a list of players. For example inside a method it is completely OK to write

IList<Player> footballTeam = ...

When using F#, it can even be OK to create a type abbreviation

type FootballTeam = IList<Player>

But when the context is broader or even unclear, you should not do this. This is especially the case, when you create a new class, where it is not clear in which context it may be used in the future. A warning sign is when you start to add additional attributes to your class (name of the team, coach, etc.). This is a clear sign that the context where the class will be used is not fixed and will change in the future. In this case you cannot consider the team as a list of players, but you should model the list of the (currently active, not injured, etc.) players as an attribute of the team.


If your class users need all the methods and properties** List has, you should derive your class from it. If they don't need them, enclose the List and make wrappers for methods your class users actually need.

This is a strict rule, if you write a public API , or any other code that will be used by many people. You may ignore this rule if you have a tiny app and no more than 2 developers. This will save you some time.

For tiny apps, you may also consider choosing another, less strict language. Ruby, JavaScript - anything that allows you to write less code.


設計>實施

你公開哪些方法和屬性是設計決定。 您繼承的基類是實現細節。 我覺得值得回到前者。

一個對像是數據和行為的集合。

所以你的第一個問題應該是:

  • 這個對像在我創建的模型中包含哪些數據?
  • 該對像在該模型中展現了什麼行為?
  • 未來如何改變?

請記住,繼承意味著一個“isa”(是)關係,而組合意味著“有一個”(hasa)關係。 根據您的觀點選擇適合您情況的產品,並考慮應用程序發展時的情況。

Consider thinking in interfaces before you think in concrete types, as some people find it easier to put their brain in "design mode" that way.

This isn't something everyone does consciously at this level in day to day coding. But if you're mulling this sort of topic, you're treading in design waters. Being aware of it can be liberating.

Consider Design Specifics

Take a look at List<T> and IList<T> on MSDN or Visual Studio. See what methods and properties they expose. Do these methods all look like something someone would want to do to a FootballTeam in your view?

Does footballTeam.Reverse() make sense to you? Does footballTeam.ConvertAll<TOutput>() look like something you want?

This isn't a trick question; the answer might genuinely be "yes". If you implement/inherit List<Player> or IList<Player>, you're stuck with them; if that's ideal for your model, do it.

If you decide yes, that makes sense, and you want your object to be treatable as a collection/list of players (behaviour), and you therefore want to implement ICollection or IList, by all means do so. Notionally:

class FootballTeam : ... ICollection<Player>
{
    ...
}

If you want your object to contain a collection/list of players (data), and you therefore want the collection or list to be a property or member, by all means do so. Notionally:

class FootballTeam ...
{
    public ICollection<Player> Players { get { ... } }
}

You might feel that you want people to be able to only enumerate the set of players, rather than count them, add to them or remove them. IEnumerable<Player> is a perfectly valid option to consider.

You might feel that none of these interfaces are useful in your model at all. This is less likely (IEnumerable<T> is useful in many situations) but it's still possible.

Anyone who attempts to tell you that one of these it is categorically and definitively wrong in every case is misguided. Anyone who attempts to tell you it is categorically and definitively right in every case is misguided.

Move on to Implementation

Once you've decided on data and behaviour, you can make a decision about implementation. This includes which concrete classes you depend on via inheritance or composition.

This may not be a big step, and people often conflate design and implementation since it's quite possible to run through it all in your head in a second or two and start typing away.

A Thought Experiment

An artificial example: as others have mentioned, a team is not always "just" a collection of players. Do you maintain a collection of match scores for the team? Is the team interchangable with the club, in your model? If so, and if your team isa collection of players, perhaps it also isa collection of staff and/or a collection of scores. Then you end up with:

class FootballTeam : ... ICollection<Player>, 
                         ICollection<StaffMember>,
                         ICollection<Score>
{
    ....
}

Design notwithstanding, at this point in C# you won't be able to implement all of these by inheriting from List<T> anyway, since C# "only" supports single inheritance. (If you've tried this malarky in C++, you may consider this a Good Thing.) Implementing one collection via inheritance and one via composition is likely to feel dirty. And properties such as Count become confusing to users unless you implement ILIst<Player>.Count and IList<StaffMember>.Count etc. explicitly, and then they're just painful rather than confusing. You can see where this is going; gut feeling whilst thinking down this avenue may well tell you it feels wrong to head in this direction (and rightly or wrongly, your colleagues might also if you implemented it this way!)

The Short Answer (Too Late)

The guideline about not inheriting from collection classes isn't C# specific, you'll find it in many programming languages. It is received wisdom not a law. One reason is that in practice composition is considered to often win out over inheritance in terms of comprehensibility, implementability and maintainability. It's more common with real world / domain objects to find useful and consistent "hasa" relationships than useful and consistent "isa" relationships unless you're deep in the abstract, most especially as time passes and the precise data and behaviour of objects in code changes. This shouldn't cause you to always rule out inheriting from collection classes; but it may be suggestive.


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

以前的代碼意味著:一群來自街頭踢球的球員,他們恰好有一個名字。 就像是:

無論如何,這個代碼(從我的答案)

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    public int PlayerCount
    {
    get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

意思是:這是一個擁有管理,球員,管理員等的足球隊。例如:

這是你的邏輯如何呈現在圖片中......


I just wanted to add that Bertrand Meyer, the inventor of Eiffel and design by contract, would have Team inherit from List<Player> without so much as batting an eyelid.

In his book, Object-Oriented Software Construction , he discusses the implementation of a GUI system where rectangular windows can have child windows. He simply has Window inherit from both Rectangle and Tree<Window> to reuse the implementation.

However, C# is not Eiffel. The latter supports multiple inheritance and renaming of features . In C#, when you subclass, you inherit both the interface and the implemenation. You can override the implementation, but the calling conventions are copied directly from the superclass. In Eiffel, however, you can modify the names of the public methods, so you can rename Add and Remove to Hire and Fire in your Team . If an instance of Team is upcast back to List<Player> , the caller will use Add and Remove to modify it, but your virtual methods Hire and Fire will be called.


What the guidelines say is that the public API should not reveal the internal design decision of whether you are using a list, a set, a dictionary, a tree or whatever. A "team" is not necessarily a list. You may implement it as a list but users of your public API should use you class on a need to know basis. This allows you to change your decision and use a different data structure without affecting the public interface.


Let me rewrite your question. so you might see the subject from a different perspective.

When I need to represent a football team, I understand that it is basically a name. Like: "The Eagles"

string team = new string();

Then later I realized teams also have players.

Why can't I just extend the string type so that it also holds a list of players?

Your point of entry into the problem is arbitrary. Try to think what does a team have (properties), not what it is .

After you do that, you could see if it shares properties with other classes. And think about inheritance.


Does allowing people to say

myTeam.subList(3, 5);

make any sense at all? If not then it shouldn't be a List.


正如大家所指出的,一個球員隊伍不是球員名單。 這個錯誤是由許多人在任何地方做出的,或許是在各種專業水平上。 通常這個問題很微妙,偶爾也很嚴重,就像這種情況一樣。 這種設計很糟糕,因為這違反了里斯科替代原則 。 互聯網有許多好的文章來解釋這個概念,例如http://en.wikipedia.org/wiki/Liskov_substitution_principle

總之,在兩個階級之間的父母/子女關係中有兩個規則需要保留:

  • 孩子應該不需要比完全定義父母的特徵更少的特徵。
  • 除了完全定義孩子的內容之外,父母應該不需要任何特徵。

換句話說,父母是孩子的必要定義,而孩子是父母的充分定義。

這是一種思考解決方案的方法,並應用上面應該幫助避免這種錯誤的原則。 通過驗證父類的所有操作是否對派生類在結構和語義上都是有效的,應該測試一個假設。

  • 足球隊是足球隊的名單嗎? (列表中的所有屬性是否適用於同一含義的團隊)
    • 一個團隊是一組同質實體嗎? 是的,團隊是一個玩家的集合
    • 球員是否包含描述球隊狀態的順序,球隊是否確保順序被保留,除非明確改變? 不,並且沒有
    • 球員是否期望根據球隊在球隊中的排名位置被包括/放棄? 沒有

如您所見,只有列表的第一個特徵適用於團隊。 因此,一個團隊不是一個清單。 列表將是你如何管理你的團隊的實現細節,所以它應該只用於存儲玩家對象,並用Team類的方法來操縱。

在這一點上,我想說的是,在我看來,Team類應該不會使用List來實現; 它應該在大多數情況下使用Set數據結構(例如HashSet)來實現。


While I don't have a complex comparison as most of these answers do, I would like to share my method for handling this situation. By extending IEnumerable<T> , you can allow your Team class to support Linq query extensions, without publicly exposing all the methods and properties of List<T> .

class Team : IEnumerable<Player>
{
    private readonly List<Player> playerList;

    public Team()
    {
        playerList = new List<Player>();
    }

    public Enumerator GetEnumerator()
    {
        return playerList.GetEnumerator();
    }

    ...
}

class Player
{
    ...
}

When they say List<T> is "optimized" I think they want to mean that it doesn't have features like virtual methods which are bit more expensive. So the problem is that once you expose List<T> in your public API , you loose ability to enforce business rules or customize its functionality later. But if you are using this inherited class as internal within your project (as opposed to potentially exposed to thousands of your customers/partners/other teams as API) then it may be OK if it saves your time and it is the functionality you want to duplicate. The advantage of inheriting from List<T> is that you eliminate lot of dumb wrapper code that is just never going to be customized in foreseeable future. Also if you want your class to explicitly have exact same semantics as List<T> for the life of your APIs then also it may be OK.

I often see lot of people doing tons of extra work just because of FxCop rule says so or someone's blog says it's a "bad" practice. Many times, this turns code in to design pattern palooza weirdness. As with lot of guideline, treat it as guideline that can have exceptions.


Just because I think the other answers pretty much go off on a tangent of whether a football team "is-a" List<FootballPlayer> or "has-a" List<FootballPlayer> , which really doesn't answer this question as written.

The OP chiefly asks for clarification on guidelines for inheriting from List<T> :

A guideline says that you shouldn't inherit from List<T> . 為什麼不?

Because List<T> has no virtual methods. This is less of a problem in your own code, since you can usually switch out the implementation with relatively little pain - but can be a much bigger deal in a public API.

What is a public API and why should I care?

A public API is an interface you expose to 3rd party programmers. Think framework code. And recall that the guidelines being referenced are the ".NET Framework Design Guidelines" and not the ".NET Application Design Guidelines". There is a difference, and - generally speaking - public API design is a lot more strict.

If my current project does not and is not likely to ever have this public API, can I safely ignore this guideline? If I do inherit from List and it turns out I need a public API, what difficulties will I have?

Pretty much, yeah. You may want to consider the rationale behind it to see if it applies to your situation anyway, but if you're not building a public API then you don't particularly need to worry about API concerns like versioning (of which, this is a subset).

If you add a public API in the future, you will either need to abstract out your API from your implementation (by not exposing your List<T> directly) or violate the guidelines with the possible future pain that entails.

Why does it even matter? A list is a list. What could possibly change? What could I possibly want to change?

Depends on the context, but since we're using FootballTeam as an example - imagine that you can't add a FootballPlayer if it would cause the team to go over the salary cap. A possible way of adding that would be something like:

 class FootballTeam : List<FootballPlayer> {
     override void Add(FootballPlayer player) {
        if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
 }

Ah...but you can't override Add because it's not virtual (for performance reasons).

If you're in an application (which, basically, means that you and all of your callers are compiled together) then you can now change to using IList<T> and fix up any compile errors:

 class FootballTeam : IList<FootballPlayer> {
     private List<FootballPlayer> Players { get; set; }

     override void Add(FootballPlayer player) {
        if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
     /* boiler plate for rest of IList */
 }

but, if you've publically exposed to a 3rd party you just made a breaking change that will cause compile and/or runtime errors.

TL;DR - the guidelines are for public APIs. For private APIs, do what you want.


首先,它與可用性有關。 如果使用繼承,則Team類將公開純粹為對像操作設計的行為(方法)。 例如, AsReadOnly()CopyTo(obj)方法對團隊對像沒有意義。 而不是AddRange(items)方法,你可能需要一個更具描述性的AddPlayers(players)方法。

如果你想使用LINQ,實現一個通用接口,如ICollection<T>IEnumerable<T>會更有意義。

如前所述,構圖是正確的方式。 只需將玩家列表作為私有變量來實現。


This reminds me of the "Is a" versus "has a" tradeoff. Sometimes it is easier and makesmore sense to inherit directly from a super class. Other times it makes more sense to create a standalone class and include the class you would have inherited from as a member variable. You can still access the functionality of the class but are not bound to the interface or any other constraints that might come from inheriting from the class.

Which do you do? As with a lot of things...it depends on the context. The guide I would use is that in order to inherit from another class there truly should be an "is a" relationship. So if you a writing a class called BMW, it could inherit from Car because a BMW truly is a car. A Horse class can inherit from the Mammal class because a horse actually is a mammal in real life and any Mammal functionality should be relevant to Horse. But can you say that a team is a list? From what I can tell, it does not seem like a Team really "is a" List. So in this case, I would have a List as a member variable.





design