c# - يرث - هل ترث الام من ابنتها المتوفاة




لماذا لا ترث من القائمة<T>؟ (17)

وأخيرًا ، يقترح البعض التفاف القائمة في شيء ما:

هذه هي الطريقة الصحيحة. "كلمة بلا معنى" هي طريقة سيئة للنظر إلى هذا. لها معنى واضح عند كتابة my_team.Players.Count . تريد عد اللاعبين.

my_team.Count

..لا يعني شيئا. عد ماذا؟

الفريق ليس قائمة - تتكون من أكثر من مجرد قائمة من اللاعبين. يمتلك الفريق لاعبين ، لذا يجب أن يكون اللاعبون جزءًا منه (عضو).

إذا كنت قلقًا حقًا من أن تكون مطوّلًا بشكل مفرط ، فيمكنك دومًا عرض الخصائص من الفريق:

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

.. الذي يصبح:

my_team.PlayerCount

هذا له معنى الآن ويلتزم قانون ديميتر .

يجب أن تفكر أيضًا في الالتزام بمبدأ إعادة الاستخدام المركب . من خلال الإرث من List<T> ، فأنت تقول أن الفريق عبارة عن قائمة باللاعبين ويكشف عن الطرق غير الضرورية للخروج منه. هذا غير صحيح - كما ذكرتم ، فإن الفريق أكثر من مجرد قائمة من اللاعبين: فهو يحمل اسمًا ، أو مديرًا ، أو أعضاء مجلس إدارة ، أو مدربين ، أو طاقم طبي ، أو قبعات راتب ، إلخ. من خلال جعل صف فريقك يحتوي على قائمة اللاعبين ، يقول "الفريق يحتوي على قائمة اللاعبين" ، ولكن يمكن أن يكون له أيضًا أشياء أخرى.

عند التخطيط لبرامجي ، غالباً ما أبدأ بسلسلة من الأفكار مثل:

فريق كرة القدم هو مجرد قائمة لاعبي كرة القدم. لذلك ، يجب عليّ تمثيلها مع:

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 ، وليس لديّ أي تحكم فيها ، لذا لا يمكنني تغييرها لاحقًا ، بعد تعريض "واجهة برمجة تطبيقات عامة" . لكنني أكافح لفهم هذا. ما هي واجهة برمجة التطبيقات العامة ولماذا يجب أن أهتم؟ إذا كان مشروعي الحالي لا يملك واجهة برمجة تطبيقات عامة على الأرجح ، فهل يمكنني تجاهل هذا المبدأ التوجيهي بأمان؟ إذا ورثت من List واتضح أنني بحاجة إلى واجهة برمجة تطبيقات عامة ، فما الصعوبات التي سأواجهها؟

لماذا لا يهم حتى؟ القائمة هي قائمة. ما الذي يمكن أن يتغير؟ ما الذي يمكن أن أرغب في تغييره؟

وأخيرًا ، إذا لم تكن مايكروسوفت تريد مني أن أرث من List ، فلماذا لم تجعل الطبقة sealed ؟

ماذا أنا من المفترض أن تستخدم؟

على ما يبدو ، بالنسبة للمجموعات المخصصة ، قامت Microsoft بتوفير فئة Collection يجب تمديدها بدلاً من List . لكن هذه الطبقة عارية للغاية ، وليس لديها العديد من الأشياء المفيدة ، مثل AddRange ، على سبيل المثال. توفر إجابة jvitor83 أساسًا منطقيًا للأداء لهذا الأسلوب المعين ، ولكن كيف يكون معدل AddRange بطيئًا وليس أفضل من AddRange ؟

إن التوارث من Collection هو عمل أكثر من الإرث من List ، ولا أرى فائدة. من المؤكد أن مايكروسوفت لم تطلب مني القيام بعمل إضافي دون سبب ، لذلك لا يسعني أن أشعر بأنني أخطئ في فهم شيء ما ، وأن وراثة Collection ليست الحل الصحيح لمشكلتي.

لقد رأيت اقتراحات مثل تطبيق IList . فقط لا. هذا هو عشرات الأسطر من التعليمات البرمجية الأساسية التي لا تكسب لي شيئا.

وأخيرًا ، يقترح البعض التفاف List في شيء ما:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

هناك مشكلتان مع هذا:

  1. يجعل كود بلادي مطول بلا داع. يجب أن أتصل الآن my_team.Players.Count بدلاً من my_team.Count فقط. الحمد لله ، مع C # يمكنني تحديد مفهرسات لجعل الفهرسة شفافة ، وإلى الأمام جميع أساليب القائمة الداخلية ... ولكن هذا كثير من التعليمات البرمجية! ماذا أحصل على كل هذا العمل؟

  2. انها مجرد لا معنى له. لا يملك فريق كرة القدم "قائمة" باللاعبين. إنها قائمة اللاعبين. لا تقول "انضم جون ماكفوت بولر إلى لاعبي SomeTeam". أنت تقول "جون قد انضم إلى SomeTeam". لا تضيف حرفًا إلى "أحرف السلسلة" ، فأنت تضيف حرفًا إلى سلسلة. لا تضيف كتابًا إلى كتب المكتبة ، بل تضيف كتابًا إلى المكتبة.

أدرك أن ما يحدث "تحت غطاء المحرك" يمكن أن يقال أنه "يضيف قائمة X إلى Y الداخلية" ، لكن هذا يبدو وكأنه طريقة تفكير غير بديهية للغاية حول العالم.

سؤالي (ملخص)

ما هي الطريقة الصحيحة C # لتمثيل بنية البيانات ، والتي ، "منطقياً" (أي "للعقل البشري") هي مجرد list things مع القليل من الأجراس والصفارات؟

هل الموروث من List<T> دائمًا غير مقبول؟ متى يكون مقبولا؟ لماذا لماذا لا؟ ماذا يجب أن يفكر المبرمج ، عند تحديد ما إذا كان سيرث من List<T> أم لا؟


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.


تصميم> التنفيذ

ما هي الأساليب والخصائص التي تعرض لها هي قرار التصميم. ما الفئة الأساسية التي ترثها هي تفاصيل التنفيذ. أشعر أنه يستحق العودة إلى السابق.

الكائن هو مجموعة من البيانات والسلوك.

لذا يجب أن تكون الأسئلة الأولى الخاصة بك:

  • ما البيانات التي يشتمل عليها هذا الكائن في النموذج الذي أقوم بإنشائه؟
  • ما هو السلوك الذي يظهره هذا الكائن في هذا النموذج؟
  • كيف يمكن لهذا التغيير في المستقبل؟

Bear in mind that inheritance implies an "isa" (is a) relationship, whereas composition implies a "has a" (hasa) relationship. Choose the right one for your situation in your view, bearing in mind where things might go as your application evolves.

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.


بادئ ذي بدء ، فإنه يجب القيام به مع سهولة الاستخدام. إذا كنت تستخدم الوراثة ، فستقوم فئة Team بكشف السلوك (الطرق) المصممة تمامًا لمعالجة الكائن. على سبيل المثال ، AsReadOnly() أو CopyTo(obj) لا معنى لـ كائن الفريق. بدلاً من أسلوب AddRange(items) قد ترغب على الأرجح في أسلوب AddPlayers(players) أكثر AddPlayers(players) .

إذا كنت ترغب في استخدام LINQ ، فإن تنفيذ واجهة عامة مثل ICollection<T> أو IEnumerable<T> سيكون أكثر منطقية.

كما ذكرنا ، فإن التكوين هو الطريقة الصحيحة للتقدم. فقط قم بتطبيق قائمة اللاعبين كمتغير خاص.


ماذا لو كان لفريق FootballTeam فريق احتياطي إلى جانب الفريق الرئيسي؟

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
}

كيف يمكنك أن نموذج ذلك؟

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

من الواضح أن العلاقة لها ولا هي .

أو RetiredPlayers ؟

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
    List<FootballPlayer> RetiredPlayers { get; set; }
}

كقاعدة عامة ، إذا أردت في أي وقت أن ترث من مجموعة ، فقم بتسمية الفئة SomethingCollection .

هل SomethingCollection الخاص بك معنى منطقي؟ لا تفعل ذلك إلا إذا كان نوعك عبارة عن مجموعة من Something .

في حالة FootballTeam لا يبدو صحيحًا. Team أكثر من Collection . يمكن أن يكون لدى Team مدربين ومدربين ، إلخ ، كما أشارت الإجابات الأخرى.

يبدو أن FootballCollection يمثل مجموعة من كرات القدم أو ربما مجموعة من أدوات كرة القدم. TeamCollection ، مجموعة من الفرق.

يبدو أن FootballPlayerCollection عبارة عن مجموعة من اللاعبين التي من شأنها أن تكون اسمًا صالحًا لصف دراسي يرث من List<FootballPlayer> إذا كنت تريد فعلًا ذلك.

حقا List<FootballPlayer> هو نوع جيد تماما للتعامل معها. ربما IList<FootballPlayer> إذا كنت ستعيده من إحدى الطرق.

باختصار

اسال نفسك

  1. هو X Y ؟ أو لديه X

  2. هل تعني أسماء فصلي ما هي؟


نجاح باهر ، مشاركتك لديها مجموعة كاملة من الأسئلة والنقاط. معظم المنطق الذي تحصل عليه من Microsoft هو بالضبط على النقطة. لنبدأ بكل شيء حول List<T>

  • List<T> هي الأمثل للغاية. هو الاستخدام الرئيسي لاستخدامه كعضو خاص في كائن.
  • لم تقم Microsoft بإغلاقه لأنه قد تحتاج في بعض الأحيان إلى إنشاء فئة لها اسم أكثر صداقة: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... } . الآن الأمر سهل مثل القيام var list = new MyList<int, string>(); .
  • CA1002: لا تعرض القوائم العامة : بشكل أساسي ، حتى إذا كنت تخطط لاستخدام هذا التطبيق كمطور وحيد ، فمن المفيد تطويره مع ممارسات ترميز جيدة ، بحيث يتم غرسها فيك والطبيعة الثانية. لا يزال مسموحًا لك بالكشف عن القائمة باعتبارها IList<T> إذا كنت بحاجة إلى أي مستهلك للحصول على قائمة مفهرسة. هذا يتيح لك تغيير التنفيذ داخل فئة لاحقًا.
  • جعلت Microsoft 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.
}

هناك بعض الإجابات الجيدة هنا. أود أن أضيف لهم النقاط التالية.

ما هي الطريقة الصحيحة C # لتمثيل بنية البيانات ، والتي ، "منطقياً" (أي "للعقل البشري") هي مجرد قائمة بالأشياء مع القليل من الأجراس والصفارات؟

اسأل أي عشرة أشخاص ليس لديهم مبرمج حاسوبي على دراية بوجود كرة القدم لملء الفراغ:

A football team is a particular kind of _____

هل قال أحدهم "قائمة لاعبي كرة القدم الذين لديهم عدد قليل من الأجراس والصفارات" ، أو هل قالوا جميعاً "فريق رياضي" أم "نادي" أم "منظمة"؟ إن فكرة أن فريق كرة القدم هو نوع معين من قائمة اللاعبين هو في عقلك البشري وعقلك البشري وحده.

List<T> هي آلية . فريق كرة القدم هو كائن أعمال - أي ، كائن يمثل بعض المفاهيم الموجودة في نطاق عمل البرنامج. لا تخلطها فريق كرة القدم هو نوع من الفريق. لديها قائمة ، قائمة هي قائمة من اللاعبين . قائمة ليست نوعًا خاصًا من قائمة اللاعبين . قائمة هي قائمة اللاعبين. لذلك ، قم بإنشاء خاصية تسمى Roster وهي List<Player> . ReadOnlyList<Player> عندما تكون في ذلك ، إلا إذا كنت تعتقد أن كل من يعرف عن فريق كرة القدم يحصل على حذف اللاعبين من القائمة.

هل الموروث من List<T> دائمًا غير مقبول؟

غير مقبول لمن؟ أنا؟ لا.

متى يكون مقبولا؟

عندما تقوم ببناء آلية تمدد آلية List<T> .

ماذا يجب أن يفكر المبرمج ، عند تحديد ما إذا كان سيرث من List<T> أم لا؟

هل أقوم ببناء آلية أو كائن أعمال ؟

لكن هذا كثير من الكود! ماذا أحصل على كل هذا العمل؟

لقد أمضيت وقتًا طويلاً في كتابة سؤالك الذي كان سيأخذك لكتابة أساليب إعادة التوجيه للأعضاء ذوي الصلة من List<T> أكثر من خمسين مرة. من الواضح أنك لست خائفاً من الإسهاب ، ونحن نتحدث عن كمية صغيرة جدًا من الشفرات هنا ؛ هذا هو بضع دقائق العمل.

تحديث

لقد أعطيتها المزيد من التفكير وهناك سبب آخر لعدم تصميم فريق كرة القدم كقائمة من اللاعبين. في الواقع ، قد تكون فكرة سيئة أن نضع نموذجًا لفريق كرة القدم كأن يكون لديه قائمة باللاعبين أيضًا. مشكلة مع فريق / وجود قائمة من اللاعبين هو أن ما لديك هو لقطة للفريق في لحظة من الزمن . لا أعرف ما هي طبيعة عملك في هذا الفصل ، ولكن إذا كان لديّ صف يمثل فريق كرة قدم ، فإنني أود طرح أسئلة مثل "كم عدد لاعبي سي هوكس الذين غابوا عن الألعاب بسبب الإصابة بين عامي 2003 و 2013؟" أو "ما هو لاعب دنفر الذي لعب سابقاً لفريق آخر الذي كان أكبر زيادة سنوية في عدد يارداته؟" أو " هل ذهب الخناجر على طول الطريق هذا العام؟ "

هذا يعني أن فريق كرة القدم يبدو لي أن يكون نموذجًا جيدًا كمجموعة من الحقائق التاريخية مثل عندما يتم تجنيد اللاعب أو جرحه أو تقاعده ، إلخ. من الواضح أن قائمة اللاعبين الحالية هي حقيقة مهمة يجب أن تكون على الأرجح ولكن قد تكون هناك أشياء أخرى مثيرة للاهتمام تريد القيام بها مع هذا الكائن الذي يتطلب منظوراً أكثر تاريخية.


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 
}

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.


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();
}

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.


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.


My dirty secret: I don't care what people say, and I do it. .NET Framework is spread with "XxxxCollection" (UIElementCollection for top of my head example).

So what stops me saying:

team.Players.ByName("Nicolas")

When I find it better than

team.ByName("Nicolas")

Moreover, my PlayerCollection might be used by other class, like "Club" without any code duplication.

club.Players.ByName("Nicolas")

Best practices of yesterday, might not be the one of tomorrow. There is no reason behind most best practices, most are only wide agreement among the community. Instead of asking the community if it will blame you when you do that ask yourself, what is more readable and maintainable?

team.Players.ByName("Nicolas") 

أو

team.ByName("Nicolas")

هل حقا. Do you have any doubt? Now maybe you need to play with other technical constraints that prevent you to use List<T> in your real use case. But don't add a constraint that should not exist. If Microsoft did not document the why, then it is surely a "best practice" coming from nowhere.


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.


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.


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.


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

يعني: هذا هو فريق كرة القدم الذي لديه الإدارة ، واللاعبين ، والمشرفين ، الخ. شيء من هذا القبيل:

هذه هي الطريقة التي يقدم بها المنطق في الصور ...







design