method - what is the proper syntax to inherit from a base class in c#?




Почему не наследовать от List<T>? (18)

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

Это правильный путь. «Безусловно многословие» - это плохой способ взглянуть на это. Он имеет явное значение, когда вы пишете 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, и я не контролирую ее, поэтому я не могу ее изменить позже, после публикации «общедоступного 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> или нет?


Каков правильный C # способ представления структуры данных ...

Remeber: «Все модели ошибочны, но некоторые из них полезны». - Джордж EP Box

Нет «правильного пути», только полезного.

Выберите тот, который вам полезен и / ваши пользователи. Вот и все. Развивайте экономически, не переучивайте. Чем меньше кода вы пишете, тем меньше кода вам нужно будет отлаживать. (читайте следующие издания).

- Отредактировано

Мой лучший ответ был бы ... это зависит. Наследование из списка приведет к тому, что клиенты этого класса будут подвергнуты методам, которые могут не отображаться, прежде всего потому, что FootballTeam выглядит как бизнес-объект.

- Издание 2

Я искренне не помню, что я имел в виду в комментарии «Не переучивайте». Хотя я считаю, что мышление KISS является хорошим руководством, я хочу подчеркнуть, что наследование бизнес-класса из List создаст больше проблем, чем устраняет, из-за утечки абстракции .

С другой стороны, я считаю, что существует ограниченное число случаев, когда просто наследовать из Списка полезно. Как я писал в предыдущем издании, все зависит. Ответ на каждый случай сильно зависит от знания, опыта и личных предпочтений.

Спасибо @kai за то, что помогли мне более точно рассказать о ответе.


Это зависит от контекста

Когда вы рассматриваете свою команду как список игроков, вы проецируете «идею» команды ногой ногой на один аспект: вы уменьшаете «команду» до людей, которых вы видите на поле. Эта проекция верна только в определенном контексте. В другом контексте это может быть совершенно неправильно. Представьте, что вы хотите стать спонсором команды. Поэтому вам нужно поговорить с менеджерами команды. В этом контексте команда прогнозируется в списке своих менеджеров. И эти два списка обычно не перекрываются очень сильно. Другими контекстами являются текущие и прежние игроки и т. Д.

Неявная семантика

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

Классы расширяемы

Когда вы используете класс только с одним членом (например IList activePlayers), вы можете использовать имя члена (и, кроме того, его комментарий), чтобы сделать контекст понятным. Когда есть дополнительные контексты, вы просто добавляете дополнительный член.

Классы сложнее

В некоторых случаях может быть излишним создать дополнительный класс. Каждое определение класса должно быть загружено через загрузчик классов и будет кэшироваться виртуальной машиной. Это требует производительности и памяти во время выполнения. Когда у вас есть очень специфический контекст, может быть, стоит подумать о футбольной команде как о списке игроков. Но в этом случае вы должны просто использовать a IList, а не класс, полученный из него.

Заключение / соображения

Когда у вас есть очень специфический контекст, вполне нормально рассматривать команду в качестве списка игроков. Например, внутри метода вполне нормально писать

IList<Player> footballTeam = ...

При использовании F #, может даже быть ОК, чтобы создать аббревиатуру типа

type FootballTeam = IList<Player>

Но когда контекст шире или даже неясен, вы не должны этого делать. Это особенно важно, когда вы создаете новый класс, где неясно, в каком контексте он может использоваться в будущем. Предупреждающий знак - это когда вы начинаете добавлять дополнительные атрибуты в свой класс (имя команды, тренера и т. Д.). Это явный признак того, что контекст, в котором будет использоваться класс, не является фиксированным и изменится в будущем. В этом случае вы не можете рассматривать команду как список игроков, но вы должны моделировать список игроков (в настоящее время активных, а не травмированных и т. Д.) В качестве атрибута команды.


Дизайн> Реализация

Какие методы и свойства вы раскрываете, это дизайнерское решение. Какой базовый класс вы наследуете, это деталь реализации. Я чувствую, что стоит сделать шаг назад к первому.

Объект представляет собой совокупность данных и поведения.

Поэтому ваши первые вопросы должны быть следующими:

  • Какие данные содержит этот объект в модели, которую я создаю?
  • Какое поведение демонстрирует этот объект в этой модели?
  • Как это может измениться в будущем?

Имейте в виду, что наследование подразумевает отношение «isa» (a), тогда как композиция подразумевает «имеет» (hasa) связь. Выберите правильный вариант для вашей ситуации в своем представлении, учитывая, что ситуация может измениться по мере развития вашего приложения.

Подумайте об интерфейсах, прежде чем думать о конкретных типах, так как некоторым людям проще разместить свой мозг в «режиме дизайна».

Это не то, что каждый делает сознательно на этом уровне в повседневной кодировке. Но если вы обдумываете такую ​​тему, вы стучите в проектные воды. Осознание этого может быть освобождением.

Рассмотрите спецификацию дизайна

Взгляните на список <T> и IList <T> на MSDN или Visual Studio. Посмотрите, какие методы и свойства они выставляют. У вас все эти методы похожи на то, что кто-то хотел бы сделать с FootballTeam на ваш взгляд?

Имеет ли значение footballTeam.Reverse () смысл? Будет ли footballTeam.ConvertAll <TOutput> () выглядеть как-то, что вы хотите?

Это не спорный вопрос; ответ может быть действительно «да». Если вы реализуете / наследуете List <Player> или IList <Player>, вы застряли с ними; если это идеально подходит для вашей модели, сделайте это.

Если вы решите «да», это имеет смысл, и вы хотите, чтобы ваш объект обрабатывался как коллекция / список игроков (поведение), и поэтому вы хотите реализовать ICollection или IList, во что бы то ни стало. Номинально:

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

Если вы хотите, чтобы ваш объект содержал коллекцию / список игроков (данных), и поэтому вы хотите, чтобы коллекция или список являлись свойством или членом, непременно сделайте это. Номинально:

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

Вы можете почувствовать, что хотите, чтобы люди могли только перечислять множество игроков, а не считать их, добавлять к ним или удалять их. IEnumerable <Player> - вполне допустимый вариант для рассмотрения.

Вы можете почувствовать, что ни один из этих интерфейсов не является полезным в вашей модели вообще. Это менее вероятно (IEnumerable <T> полезен во многих ситуациях), но это все еще возможно.

Любой, кто пытается сказать вам, что один из них является категорически и окончательно ошибочным в каждом случае, ошибочен . Любой, кто пытается сказать вам это категорически и окончательно правильно, в каждом случае ошибочно.

Перейдите к реализации

После того, как вы решите данные и поведение, вы можете принять решение о реализации. Это включает в себя конкретные классы, от которых вы зависите через наследование или состав.

Это не может быть большим шагом, и люди часто сталкиваются с дизайном и реализацией, так как вполне возможно пропустить все это в вашей голове через секунду или два и начать печатать.

Эксперимент с мыслями

Искусственный пример: как отмечали другие, команда не всегда «всего лишь» является коллекцией игроков. Поддерживаете ли вы коллекцию партитур для команды? Является ли команда взаимозаменяема с клубом, в вашей модели? Если это так, и если ваша команда является коллекцией игроков, возможно, она также является коллекцией персонала и / или коллекцией баллов. Затем вы получите:

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

Несмотря на дизайн, на данный момент в C # вы не сможете реализовать все это путем наследования от List <T> в любом случае, поскольку C # «только» поддерживает одно наследование. (Если вы уже пробовали это malarky в C ++, вы можете рассмотреть это хорошая вещь.) Реализация одной коллекции с помощью наследования и один с помощью композиции, вероятно, чувствовать себя грязной. И такие свойства, как Count, становятся запутанными для пользователей, если вы явно не реализуете ILIst <Player> .Count и IList <StaffMember> .Count и т. Д., А затем они просто больны, а не запутывают. Вы можете видеть, где это происходит; но в то время как мышление по этому проспекту вполне может сказать вам, что ему нехорошо идти в этом направлении (и правильно или неправильно, ваши коллеги могут также, если бы вы реализовали его таким образом!)

Короткий ответ (слишком поздно)

Руководство по не наследованию классов коллекций не является специфичным для C #, вы найдете его на многих языках программирования. Получается мудрость, а не закон. Одна из причин заключается в том, что на практике считается, что композиция часто выигрывает над наследованием с точки зрения понятности, реализуемости и ремонтопригодности. Чаще всего с объектами реального мира / домена можно найти полезные и последовательные отношения «hasa», чем полезные и последовательные отношения «isa», если вы не вникаете в абстрактные, особенно с течением времени и точные данные и поведение объектов в коде изменения. Это не должно заставлять вас всегда исключать наследование из классов коллекции; но это может быть наводящим на размышления.


Как все отметили, команда игроков - это не список игроков. Эта ошибка совершается многими людьми во всем мире, возможно, на разных уровнях знаний. Часто проблема тонкая, а иногда и очень грубая, как в этом случае. Такие проекты плохи, потому что они нарушают Принцип замещения Лискова . В Интернете есть много хороших статей, объясняющих эту концепцию, например, http://en.wikipedia.org/wiki/Liskov_substitution_principle

Таким образом, существует два правила, которые должны быть сохранены в отношениях между родителями и детьми между классами:

  • Ребенок не должен иметь никакой характеристики, которая меньше, чем то, что полностью определяет Родитель.
  • Родитель не должен требовать никаких признаков в дополнение к тому, что полностью определяет Ребенка.

Другими словами, родитель является необходимым определением ребенка, а ребенок является достаточным определением родителя.

Вот способ продумать решение и применить вышеприведенный принцип, который поможет избежать такой ошибки. Нужно проверить гипотезу, проверяя, являются ли все операции родительского класса действительными для производного класса как структурно, так и семантически.

  • Есть ли в футбольной команде список футболистов? (Все свойства списка относятся к команде в том же значении)
    • Является ли команда коллекцией однородных образований? Да, команда - это коллекция игроков
    • Является ли порядок включения игроков в описание состояния команды и обеспечивает ли команда сохранение последовательности без явного изменения? Нет, и нет
    • Ожидается ли, что игроки будут включены / сброшены на основе их последовательной позиции в команде? нет

Как вы видите, только первая характеристика списка применима к команде. Следовательно, команда не является списком. Список будет деталью реализации того, как вы управляете своей командой, поэтому его следует использовать только для хранения объектов игрока и манипулирования ими с помощью методов класса Team.

На этом этапе я хотел бы отметить, что класс Team должен, на мой взгляд, даже не реализовываться с использованием List; он должен быть реализован с использованием структуры данных Set (например, HashSet), в большинстве случаев.


Ничего себе, ваш пост имеет целую массу вопросов и очков. Большинство аргументов, которые вы получаете от 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.
}

Что, если у FootballTeam есть команда по резервам вместе с основной командой?

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

Как бы вы это сформулировали?

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

Очевидно, что отношение имеет a, а не a .

или 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> если вы действительно этого хотели.

Really List<FootballPlayer> - отличный способ борьбы. Возможно, IList<FootballPlayer> если вы возвращаете его из метода.

В итоге

Спроси себя

  1. Является ли X a Y ? или имеет X a Y ?

  2. Знают ли мои имена классов, каковы они?


Это классический пример composition и inheritance .

В этом конкретном случае:

Является ли команда списком игроков с добавленным поведением

или же

Является ли команда собственным объектом, который содержит список игроков.

Расширяя список, вы ограничиваете себя несколькими способами:

  1. Вы не можете ограничить доступ (например, остановить людей, изменяющих список). Вы получаете все методы List, хотите ли вы / хотите их всех или нет.

  2. Что произойдет, если вы хотите иметь списки других вещей. Например, в командах есть тренеры, менеджеры, болельщики, оборудование и т. Д. Некоторые из них вполне могут быть списками в своем собственном праве.

  3. Вы ограничиваете свои варианты наследования. Например, вы можете создать общий объект Team, а затем получить BaseballTeam, FootballTeam и т. Д. Чтобы наследовать List, вам нужно наследовать от Team, но это означает, что все команды разных типов вынуждены иметь одну и ту же реализацию этого списка.

Композиция - включая объект, дающий поведение, которое вы хотите в своем объекте.

Наследование - ваш объект становится экземпляром объекта, который имеет поведение, которое вы хотите.

Оба имеют свое применение, но это явный случай, когда композиция предпочтительнее.


Думаю, я не согласен с вашим обобщением. Команда - это не просто коллекция игроков. У команды есть гораздо больше информации об этом - имя, эмблема, сборник управленческого / административного персонала, сбор коучинговой команды, а затем сбор игроков. Таким образом, ваш класс FootballTeam должен иметь 3 коллекции, а не собственную коллекцию; если нужно правильно моделировать реальный мир.

Вы можете рассмотреть класс PlayerCollection, который, как специализированный StringCollection, предлагает некоторые другие возможности - например, проверку и проверку перед добавлением или удалением объектов из внутреннего хранилища.

Возможно, понятие «PlayerCollection» подходит для вашего предпочтительного подхода?

public class PlayerCollection : Collection<Player> 
{ 
}

И тогда FootballTeam может выглядеть так:

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

Если вашим пользователям класса нужны все методы и свойства ** Список, вы должны извлечь из него свой класс. Если они им не нужны, приложите список и создайте обертки для методов, которые действительно нужны пользователям класса.

Это строгое правило, если вы пишете публичный API или любой другой код, который будет использоваться многими людьми. Вы можете игнорировать это правило, если у вас есть крошечное приложение и не более 2 разработчиков. Это сэкономит вам время.

Для крошечных приложений вы также можете выбрать другой, менее строгий язык. Ruby, JavaScript - все, что позволяет вам писать меньше кода.


Когда они говорят, что List<T>это «оптимизировано», я думаю, они хотят иметь в виду, что у него нет функций, таких как виртуальные методы, которые стоят дороже. Таким образом, проблема заключается в том, что после того, как вы открываете List<T>свой публичный API , вы теряете способность применять бизнес-правила или настраивать свои функции позже. Но если вы используете этот унаследованный класс как внутренний в своем проекте (в отличие от потенциально подверженного тысячам ваших клиентов / партнеров / других команд как API), тогда это может быть ОК, если это экономит ваше время, и это функциональность, которую вы хотите дублировать. Преимущество наследования List<T>заключается в том, что вы устраняете много немого кода обертки, который никогда не будет настроен в обозримом будущем. Также, если вы хотите, чтобы ваш класс явно имел ту же семантику, что иList<T> для жизни ваших API, то также может быть и ОК.

Я часто вижу, что многие люди делают тонны дополнительной работы только из-за правила FxCop, так говорят, или чей-то блог говорит, что это «плохая» практика. Много раз, это превращает код в дизайн палодовости паттерн. Как и в случае с множеством рекомендаций, относитесь к нему как к руководящему принципу, который может иметь исключения.


Мой грязный секрет: мне все равно, что говорят люди, и я это делаю. .NET Framework распространяется с помощью «XxxxCollection» (UIElementCollection для примера моей головы).

Итак, что мешает мне говорить:

team.Players.ByName("Nicolas")

Когда я нахожу это лучше, чем

team.ByName("Nicolas")

Более того, мой PlayerCollection может использоваться другим классом, например «Club», без дублирования кода.

club.Players.ByName("Nicolas")

Лучшая практика вчерашнего дня, возможно, не завтра. Нет оснований для большинства лучших практик, большинство из них являются лишь широким соглашением между сообществом. Вместо того, чтобы спрашивать сообщество, если он будет обвинять вас, когда вы это зададите себе, что более читаемо и обслуживается?

team.Players.ByName("Nicolas") 

или же

team.ByName("Nicolas")

В самом деле. У вас есть какие-то сомнения? Теперь, возможно, вам нужно играть с другими техническими ограничениями, которые не позволяют использовать List <T> в вашем реальном прецеденте. Но не добавляйте ограничение, которое не должно существовать. Если Microsoft не документировала, почему, то это, безусловно, «лучшая практика», исходящая из ниоткуда.


Просто потому, что я думаю, что другие ответы в значительной степени касаются касательной того, является ли футбольная команда «is-a» List<FootballPlayer>или «has-a» List<FootballPlayer>, которая на самом деле не отвечает на этот вопрос, как написано.

ОП в основном просит уточнить руководящие принципы для наследования List<T>:

В руководстве говорится, что вы не должны наследовать List<T>. Почему бы и нет?

Потому List<T>что не имеет виртуальных методов. Это меньше проблем в вашем собственном коде, поскольку вы обычно можете отключить реализацию с относительно небольшой болью - но это может быть гораздо больше в публичном API.

Что такое публичный API и почему меня это беспокоит?

Открытый API - это интерфейс, который вы предоставляете сторонним программистам. Подумайте о каркасе. И напомним, что ссылки, на которые ссылаются, - это « Рекомендации по разработке .NET Framework », а не « Рекомендации по разработке приложений .NET ». Есть разница, и, вообще говоря, публичный дизайн API намного более строгий.

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

В значительной степени, да. Возможно, вам стоит подумать об обоснованности этого вопроса, чтобы убедиться, что оно применимо к вашей ситуации в любом случае, но если вы не создаете публичный API, вам не нужно беспокоиться об особенностях API, таких как управление версиями (из которых это подмножество).

Если вы добавите публичный API в будущем, вам нужно будет либо абстрагировать свой API от вашей реализации (не подвергая вас List<T>непосредственно), либо нарушать рекомендации с возможной будущей болью, которая влечет за собой.

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

Зависит от контекста, но поскольку мы используем FootballTeamв качестве примера - представьте, что вы не можете добавить a, FootballPlayerесли это приведет к тому, что команда перейдет на ограничение зарплаты. Возможным способом добавления этого будет что-то вроде:

 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!");
        }
     }
 }

Ах ... но вы не можете, override Addпотому что это не так virtual(по соображениям производительности).

Если вы находитесь в приложении (что в основном означает, что вы и все ваши собеседники скомпилированы вместе), вы можете теперь изменить на использование IList<T>и исправить любые ошибки компиляции:

 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 */
 }

но, если вы публично выступили с третьим лицом, вы только что сделали перерыв, который приведет к ошибкам компиляции и / или времени выполнения.

TL; DR - рекомендации для общедоступных API. Для частных API выполните то, что вы хотите.


Футбольная команда не является списком футболистов. Футбольная команда состоит из списка футболистов!

Это логично неправильно:

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

и это правильно:

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

Что говорится в рекомендациях, так это то, что публичный API не должен раскрывать внутреннее дизайнерское решение о том, используете ли вы список, набор, словарь, дерево или что-то еще. «Команда» не обязательно является списком. Вы можете реализовать его как список, но пользователи вашего общедоступного API должны использовать ваш класс в необходимости знать основы. Это позволяет вам изменить свое решение и использовать другую структуру данных, не затрагивая публичный интерфейс.


Это зависит от поведения вашего «командного» объекта. Если он ведет себя точно так же, как в коллекции, возможно, это будет нормально представлять его сначала с помощью простого списка. Тогда вы можете начать замечать, что вы продолжаете дублировать код, который повторяется в списке; на данный момент у вас есть возможность создать объект FootballTeam, который обертывает список игроков. Класс FootballTeam становится домом для всего кода, который повторяется в списке игроков.

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

Инкапсуляция. Ваши клиенты не должны знать, что происходит внутри FootballTeam. Для всех ваших клиентов это может быть реализовано путем просмотра списка игроков в базе данных. Им не нужно знать, и это улучшает ваш дизайн.

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

Точно :) вы скажете footballTeam.Add (john), а не footballTeam.List.Add (john). Внутренний список не будет виден.


Я просто хотел добавить, что Бертран Мейер, изобретатель Эйфеля и дизайн по контракту, мог бы Teamнаследовать из-за того , что не мог List<Player>моргнуть веком.

В своей книге « Объектно-ориентированная разработка программного обеспечения» он обсуждает реализацию системы графического интерфейса, в которой прямоугольные окна могут иметь дочерние окна. Он просто Windowунаследовал от обоих Rectangleи Tree<Window>повторное использование реализации.

Однако C # не является Eiffel. Последний поддерживает множественное наследование и переименование функций . В C #, когда вы выполняете подкласс, вы наследуете интерфейс и реализацию. Вы можете переопределить реализацию, но соглашения о вызовах копируются непосредственно из суперкласса. Однако в Eiffel вы можете изменить имена общедоступных методов, чтобы вы могли переименовывать, Addа также Removeв Hireи Fireв вашем Team. Если экземпляр Teamupback снова возвращается List<Player>, вызывающий будет использовать Addи Removeизменять его, но ваши виртуальные методы Hireи Fireбудут вызваны.


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

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

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







inheritance