[C#] 為什麼不從List <T>繼承?


Answers

最後,有人建議將List包裝在一些東西中:

這是正確的方法。 “不必要的羅嗦”是看這個不好的方法。 它在寫my_team.Players.Count時有明確的含義。 你想要統計球員。

my_team.Count

沒有任何意義。 算什麼?

一個團隊不是一個清單 - 不僅僅是一個球員列表。 一個球隊擁有球員,所以球員應該成為球員的一部分(一名成員)。

如果你真的擔心它過於冗長,你可以隨時暴露團隊的屬性:

public int PlayerCount {
    get {
        return Players.Count;
    }
}

成為:

my_team.PlayerCount

這現在有意義並遵守得墨忒耳定律

您還應該考慮遵守複合重用原則 。 通過從List<T>繼承,你說的是一個團隊是一個球員列表,並暴露出不必要的方法。 這是不正確的 - 正如你所說的,一個團隊不僅僅是一個球員名單:它有一個名字,經理,董事會成員,教練,醫務人員,工資帽等等。通過讓你的團隊級別包含一個球員名單,你'說一支球隊有一份球員名單',但它也可以有其他的東西。

Question

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

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

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>時,程序員必須考慮什麼?




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.
}

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

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




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 
}



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

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

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




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.




What is the correct C# way of representing a data structure...

Remeber, "All models are wrong, but some are useful." - George EP Box

There is no a "correct way", only a useful one.

Choose one that is useful to you and/your users. 而已。 Develop economically, don't over-engineer. The less code you write, the less code you will need to debug. (read the following editions).

-- Edited

My best answer would be... it depends. Inheriting from a List would expose the clients of this class to methods that may be should not be exposed, primarily because FootballTeam looks like a business entity.

-- Edition 2

I sincerely don't remember to what I was referring on the “don't over-engineer” comment. While I believe the KISS mindset is a good guide, I want to emphasize that inheriting a business class from List would create more problems than it resolves, due abstraction leakage .

On the other hand, I believe there are a limited number of cases where simply to inherit from List is useful. As I wrote in the previous edition, it depends. The answer to each case is heavily influenced by both knowledge, experience and personal preferences.

Thanks to @kai for helping me to think more precisely about the answer.




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

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

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

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

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

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

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

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




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.




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
{
    ...
}



It depends on the behaviour of your "team" object. If it behaves just like a collection, it might be OK to represent it first with a plain List. Then you might start to notice that you keep duplicating code that iterates on the list; at this point you have the option of creating a FootballTeam object that wraps the list of players. The FootballTeam class becomes the home for all the code that iterates on the list of players.

It makes my code needlessly verbose. I must now call my_team.Players.Count instead of just my_team.Count. Thankfully, with C# I can define indexers to make indexing transparent, and forward all the methods of the internal List... But that's a lot of code! What do I get for all that work?

Encapsulation. Your clients need not know what goes on inside of FootballTeam. For all your clients know, it might be implemented by looking the list of players up in a database. They don't need to know, and this improves your design.

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 don't add a letter to "a string's characters", you add a letter to a string. You don't add a book to a library's books, you add a book to a library.

Exactly :) you will say footballTeam.Add(john), not footballTeam.List.Add(john). The internal list will not be visible.




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.




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.




Links