[c#] List <T>から継承しないのはなぜですか?


Answers

最後に、リストに何かをラップすることを提案します:

それが正しい方法です。 「不必要に言葉遣い」は、これを見る悪い方法です。 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>から継承すべきではないというガイドラインがあることが判明しました。 私はこのガイドラインに2つの点で徹底的に混乱しています。

何故なの?

明らかに、 Listはパフォーマンスのために何らかの形で最適化されています 。 どうして? Listを拡張すると、どのようなパフォーマンス上の問題が発生しますか? 何が壊れますか?

私が見たもう1つの理由は、 ListはMicrosoftによって提供されており、私はそれを制御できないため、後で「公開API」を公開した後で変更することはできません 。 しかし、私はこれを理解するのに苦労します。 パブリックAPIとは何ですか、なぜ私は気にする必要がありますか? 私の現在のプロジェクトでこのパブリックAPIを使用することができない場合、このガイドラインを無視しても問題ありませんか? 私がListから継承しそれが公開されたAPIが必要な場合は、どのような困難がありますか?

それはなぜ重要なのでしょうか? リストはリストです。 何が変わる可能性がありますか? 私はおそらく何を変えたいのですか?

最後に、MicrosoftがListから継承しないようにしたら、どうしてクラスをsealedないのですか?

私は他に何を使うべきですか?

どうやら、カスタムコレクションの場合、MicrosoftではListクラスではなくCollectionクラスを提供しています。 しかし、このクラスは非常に裸であり、 例えばAddRangeような多くの有用なものはありません。 jvitor83の答えは、その特定のメソッドのパフォーマンスの理論的根拠を提供しますが、遅いAddRangeAddRangeなしより優れていませAddRangeか?

Collectionから継承するのは、 List継承するよりももっと仕事ですが、私は何の利益も見ません。 確かにマイクロソフトでは何の理由もなく余計な作業をするようには言わないので、何か誤解しているような気持ちにはならないし、 Collectionを継承することは実際に私の問題の正しい解決策ではない。

私はIList実装などの提案を見てきました。 ちょうどいいえ。 これは何も得られない定型コードの数十行です。

最後に、 Listに何かをラップすることを提案します:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

これには2つの問題があります。

  1. それは私のコードを不必要に冗長にします。 my_team.Players.Countではなくmy_team.Players.Countを呼び出す必要があります。 ありがたいことに、C#ではインデクシングを透明にするためのインデクサーを定義し、内部Listすべてのメソッドを転送することができます...しかし、それはたくさんのコードです! そのすべての仕事のために私は何を得ますか?

  2. それはまったく平凡ではありません。 サッカーチームは選手のリストを持っていません。 それ選手のリストです。 あなたは「John McFootballerがSomeTeamの選手に加わった」とは言わない。 あなたは「ジョンはSomeTeamに加わった」と言う。 "文字列の文字"に文字を追加しないで、文字列に文字を追加します。 あなたは図書を図書館の本に追加せず、図書を図書館に追加します。

「ボンネットの下で」何が起こるかは「Yを内部リストに追加する」と言うことができますが、これは世界を考える直感的な方法のように思えます。

私の質問(要約)

「論理的に」(すなわち、「人間の心に」)というデータ構造を表現する正しいC#の方法は、いくつかの鐘や笛を持つthings listにすぎませんか?

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 
}



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.




まず第一に、使いやすさと関係しています。 継承を使用する場合、 Teamクラスはオブジェクト操作用に設計された動作(メソッド)を公開します。 たとえば、 AsReadOnly()またはCopyTo(obj)メソッドは、チームオブジェクトに意味をなさない。 AddRange(items)メソッドの代わりに、おそらくよりわかりやすいAddPlayers(players)メソッドが必要です。

LINQを使用する場合は、 ICollection<T>IEnumerable<T>などの汎用インターフェイスを実装する方が理にかなっています。

言及したように、構図は正しいことです。 プライベート変数として選手のリストを実装するだけです。




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



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.




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.




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.




誰もが指摘しているように、選手のチームは選手のリストではありません。 このミスは世界中の多くの人々によって、おそらく様々なレベルの専門知識によって作られています。 この問題のように、問題は微妙で時には非常に重大です。 そのようなデザインは、 Liskov Substitution Principleに違反するため、悪いものです。 インターネットには、この概念を説明する多くの良い記事があります。たとえば、 http://en.wikipedia.org/wiki/Liskov_substitution_principle

要約すると、クラス間の親子関係には2つのルールが保持されます。

  • 子供は、親を完全に定義するものよりも特性を要求してはならない。
  • 親は子を完全に定義するものに加えて特性を要求してはならない。

言い換えると、Parentは子供の必要な定義であり、子供はParentの十分な定義です。

ここでは、1つの解決策を考え、そのような間違いを避けるために役立つ上記の原則を適用する方法があります。 親クラスのすべての操作が派生クラスに対して構造的および意味的に有効であるかどうかを検証することによって、仮説をテストする必要があります。

  • サッカーチームはサッカー選手のリストですか? (リストのすべてのプロパティは同じ意味のチームに適用されます)
    • チームは同種の団体の集まりですか? はい、チームは選手の集まりです
    • チームの状態を説明するプレイヤーを含む順番であり、チームは明示的に変更されない限り、シーケンスが保持されることを保証していますか? いいえ、いいえ
    • チームのシーケンシャルポジションに基づいてプレーヤーのインクルード/ドロップが予想されますか? いいえ

ご覧のように、リストの最初の特徴だけがチームに適用されます。 したがって、チームはリストではありません。 リストは、チームをどのように管理するかに関する実装の詳細なので、プレーヤオブジェクトを格納し、Teamクラスのメソッドで操作する必要があります。

この時点では、チームクラスは、私の意見では、Listを使って実装すべきではないことを述べたいと思います。 ほとんどの場合、Setデータ構造(たとえば、HashSet)を使用して実装する必要があります。




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.




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.




Related