[c#] Почему не наследовать от List <T>?


11 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> . Я полностью смущен этим руководством в двух отношениях.

Почему нет?

По-видимому, List оптимизирован для производительности . Как так? Какие проблемы с производительностью я вызову, если я продолжу List ? Что именно сломается?

Еще одна причина, по которой я видел, заключается в том, что List предоставляется Microsoft, и я не контролирую ее, поэтому я не могу ее изменить позже, после публикации «общедоступного API» . Но я не могу это понять. Что такое публичный API и почему меня это беспокоит? Если мой текущий проект не имеет и вряд ли когда-либо будет иметь этот общедоступный API, могу ли я смело игнорировать это руководство? Если я наследую List и мне кажется, что мне нужен публичный API, какие трудности у меня есть?

Почему это даже имеет значение? Список - это список. Что может измениться? Что я могу изменить?

И, наконец, если Microsoft не хочет, чтобы я унаследовал от List , почему они не сделали класс sealed ?

Что еще я должен использовать?

По-видимому, для пользовательских коллекций Microsoft предоставила класс Collection который должен быть расширен вместо List . Но этот класс очень голый и не имеет много полезных вещей, например, AddRange . Ответ jvitor83 обеспечивает обоснование производительности для этого конкретного метода, но как медленный AddRange не лучше, чем No AddRange ?

Наследование из Collection - это скорее работа, чем наследование от List , и я не вижу никакой выгоды. Конечно, Microsoft не сказала бы мне, чтобы я делал дополнительную работу без причины, поэтому я не могу не чувствовать себя так, как будто я что-то не понимаю, и наследование Collection на самом деле не является правильным решением для моей проблемы.

Я видел такие предложения, как внедрение IList . Просто нет. Это десятки строк кода шаблона, который ничего мне не приносит.

Наконец, некоторые предлагают обернуть List в чем-то:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

Есть две проблемы:

  1. Это делает мой код излишне подробным. Теперь я должен назвать my_team.Players.Count вместо my_team.Count . К счастью, с C # я могу определить индексаторов, чтобы сделать индексирование прозрачным, и переслать все методы внутреннего List ... Но это много кода! Что я получу за все это?

  2. Это просто не имеет никакого смысла. Футбольная команда не имеет «списка» игроков. Это список игроков. Вы не говорите: «Джон Макфуталлер присоединился к игрокам SomeTeam». Вы говорите: «Джон присоединился к SomeTeam». Вы не добавляете письмо в «символы строки», вы добавляете письмо к строке. Вы не добавляете книгу в книги библиотеки, вы добавляете книгу в библиотеку.

Я понимаю, что то, что происходит «под капотом», можно сказать, «добавление X в список Y», но это похоже на очень противоречивый способ мышления о мире.

Мой вопрос (обобщенный)

Каков правильный метод C # для представления структуры данных, который «логически» (то есть «для человеческого разума») - это всего лишь list things с несколькими колокольчиками и свистами?

Наследуется ли List<T> всегда неприемлемо? Когда это приемлемо? Почему, почему нет? Что должен учитывать программист при решении вопроса о наследовании от List<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.




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

Средства: это футбольная команда, в которой есть руководство, игроки, администраторы и т. Д. Что-то вроде:

Вот как ваша логика представлена ​​на фотографиях ...




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.




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> будет иметь больше смысла.

Как уже упоминалось, композиция - это правильный путь. Просто реализуйте список игроков как приватную переменную.




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.




Как все отметили, команда игроков - это не список игроков. Эта ошибка совершается многими людьми во всем мире, возможно, на разных уровнях знаний. Часто проблема тонкая, а иногда и очень грубая, как в этом случае. Такие проекты плохи, потому что они нарушают Принцип замещения Лискова . В Интернете есть много хороших статей, объясняющих эту концепцию, например, 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
{
    ...
}



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 
}



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.




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.






Related