Bei der Planung meiner Programme beginne ich oft mit einer Gedankenkette wie folgt:
Eine Fußballmannschaft ist nur eine Liste von Fußballspielern. Deshalb sollte ich es vertreten mit:
var football_team = new List<FootballPlayer>();
Die Reihenfolge dieser Liste entspricht der Reihenfolge, in der die Spieler im Kader aufgeführt sind.
Später wird mir jedoch klar, dass Teams neben der bloßen Liste der Spieler auch andere Eigenschaften haben, die aufgezeichnet werden müssen. Zum Beispiel die laufende Punktzahl in dieser Saison, das aktuelle Budget, die einheitlichen Farben, die string
Darstellung des Teamnamens usw.
Also dann denke ich:
Okay, eine Fußballmannschaft ist wie eine Liste von Spielern, aber zusätzlich hat sie einen Namen (a
string
) und eine laufende Summe von Punkten (anint
). .NET bietet keine Klasse zum Speichern von Fußballmannschaften an, daher werde ich meine eigene Klasse erstellen. Die ähnlichste und relevanteste existierende Struktur istList<FootballPlayer>
, also werde ich davon erben:class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal }
Es stellt sich jedoch heraus, dass eine Richtlinie besagtList<T>
, dass Sie nicht erben sollten . Diese Richtlinie verwirrt mich in zweierlei Hinsicht.
Warum nicht?
Anscheinend List
ist irgendwie für die Leistung optimiert . Wie das? Welche Leistungsprobleme werde ich verursachen, wenn ich erweitere List
? Was genau wird brechen?
Ein weiterer Grund, den ich gesehen habe, ist, dass er List
von Microsoft bereitgestellt wird und ich keine Kontrolle darüber habe. Daher kann ich ihn später nicht mehr ändern, nachdem ich eine "öffentliche API" verfügbar gemacht habe . Aber ich habe Mühe, das zu verstehen. Was ist eine öffentliche API und warum sollte es mich interessieren? Kann ich diese Richtlinie ignorieren, wenn mein aktuelles Projekt diese öffentliche API nicht hat und wahrscheinlich nie haben wird? Welche Schwierigkeiten habe ich, wenn ich von erbe List
und es sich herausstellt, dass ich eine öffentliche API benötige?
Warum ist das überhaupt wichtig? Eine Liste ist eine Liste. Was könnte sich möglicherweise ändern? Was könnte ich möglicherweise ändern wollen?
Und wenn Microsoft nicht wollte, dass ich erbe List
, warum haben sie dann nicht die Klasse gemacht sealed
?
Was soll ich sonst noch verwenden?
Anscheinend hat Microsoft für benutzerdefinierte Sammlungen eine Collection
Klasse bereitgestellt , die anstelle von erweitert werden sollte List
. Aber diese Klasse ist sehr kahl und hat nicht viele nützliche Dinge, wieAddRange
zum Beispiel. Die Antwort von jvitor83 liefert eine Leistungsgrundlage für diese bestimmte Methode, aber wie ist eine langsame AddRange
nicht besser als nein AddRange
?
Das Erben von Collection
ist viel mehr Arbeit als das Erben von List
, und ich sehe keinen Nutzen. Sicherlich würde Microsoft mir nicht ohne Grund sagen, dass ich zusätzliche Arbeit leisten soll, daher kann ich nicht anders, als das Gefühl zu haben, etwas falsch zu verstehen, und das Erben Collection
ist eigentlich nicht die richtige Lösung für mein Problem.
Ich habe Vorschläge wie die Implementierung gesehen IList
. Einfach nein. Dies sind Dutzende von Zeilen Boilerplate-Code, die mir nichts bringen.
Schließlich schlagen einige vor, das List
in etwas einzuwickeln:
class FootballTeam
{
public List<FootballPlayer> Players;
}
Hierbei gibt es zwei Probleme:
Es macht meinen Code unnötig ausführlich. Ich muss jetzt
my_team.Players.Count
statt nur anrufenmy_team.Count
. Zum Glück kann ich mit C # Indexer definieren, um die Indizierung transparent zu machen, und alle Methoden des internen weiterleitenList
... Aber das ist viel Code! Was bekomme ich für all diese Arbeit?Es macht einfach keinen Sinn. Eine Fußballmannschaft "hat" keine Liste von Spielern. Es ist die Liste der Spieler. Sie sagen nicht "John McFootballer hat sich SomeTeams Spielern angeschlossen". Sie sagen "John ist SomeTeam beigetreten". Sie fügen "Zeichen einer Zeichenfolge" keinen Buchstaben hinzu, sondern fügen einer Zeichenfolge einen Buchstaben hinzu. Sie fügen den Büchern einer Bibliothek kein Buch hinzu, sondern fügen einer Bibliothek ein Buch hinzu.
Mir ist klar, dass das, was "unter der Haube" passiert, "X zu Ys interner Liste hinzufügen" kann, aber dies scheint eine sehr kontraintuitive Art zu sein, über die Welt zu denken.
Meine Frage (zusammengefasst)
Was ist die richtige Art und Weise C # eine Datenstruktur darstellt, die „logisch“ (das heißt, „für den menschlichen Geist“) nur ist list
der things
mit einem paar Schnickschnack?
Ist das Erben von List<T>
immer inakzeptabel? Wann ist es akzeptabel? Warum Warum nicht? Was muss ein Programmierer beachten, wenn er entscheidet, ob er erbt List<T>
oder nicht?
string
ist erforderlich, um alles zu tun, was manobject
kann und mehr .Antworten:
Hier gibt es einige gute Antworten. Ich würde ihnen die folgenden Punkte hinzufügen.
Bitten Sie zehn Nicht-Computerprogrammierer, die mit der Existenz des Fußballs vertraut sind, die Lücke auszufüllen:
Hat jemand "Liste der Fußballspieler mit ein paar Schnickschnack" gesagt, oder haben alle "Sportmannschaft" oder "Verein" oder "Organisation" gesagt? Ihre Vorstellung, dass eine Fußballmannschaft eine bestimmte Art von Spielerliste ist, liegt in Ihrem menschlichen Verstand und nur in Ihrem menschlichen Verstand.
List<T>
ist ein Mechanismus . Die Fußballmannschaft ist ein Geschäftsobjekt, dh ein Objekt, das ein Konzept darstellt, das im Geschäftsbereich des Programms liegt. Mischen Sie diese nicht! Eine Fußballmannschaft ist eine Art Mannschaft; Es hat eine Liste, eine Liste ist eine Liste von Spielern . Ein Kader ist keine bestimmte Art von Spielerliste . Ein Kader ist eine Liste von Spielern. Machen Sie also eine Eigenschaft namensRoster
aList<Player>
. Und machen Sie es,ReadOnlyList<Player>
während Sie gerade dabei sind, es sei denn, Sie glauben, dass jeder, der etwas über eine Fußballmannschaft weiß, Spieler aus dem Kader löschen kann.Inakzeptabel für wen? Mir? Nein.
Wenn Sie einen Mechanismus erstellen, der den
List<T>
Mechanismus erweitert .Baue ich einen Mechanismus oder ein Geschäftsobjekt ?
Sie haben mehr Zeit damit verbracht, Ihre Frage zu tippen, als Sie gebraucht hätten, um Weiterleitungsmethoden für die relevanten Mitglieder von
List<T>
fünfzig Mal zu schreiben . Sie haben eindeutig keine Angst vor Ausführlichkeit, und wir sprechen hier über eine sehr kleine Menge Code. Das sind ein paar Minuten Arbeit.AKTUALISIEREN
Ich habe noch etwas darüber nachgedacht und es gibt einen weiteren Grund, eine Fußballmannschaft nicht als Liste von Spielern zu modellieren. Tatsächlich könnte es eine schlechte Idee sein, eine Fußballmannschaft so zu modellieren, dass sie auch eine Liste von Spielern hat. Das Problem mit einem Team als / mit einer Liste von Spielern ist, dass Sie zu einem bestimmten Zeitpunkt eine Momentaufnahme des Teams haben . Ich weiß nicht, was Ihr Geschäftsmodell für diese Klasse ist, aber wenn ich eine Klasse hätte, die eine Fußballmannschaft repräsentiert, würde ich ihr Fragen stellen wie "Wie viele Seahawks-Spieler haben zwischen 2003 und 2013 verletzungsbedingt Spiele verpasst?" oder "Welcher Denver-Spieler, der zuvor für ein anderes Team gespielt hat, hatte im Vergleich zum Vorjahr den größten Anstieg der Yards?" oder " Sind die Piggers dieses Jahr den ganzen Weg gegangen? "
Das heißt, eine Fußballmannschaft scheint mir als Sammlung historischer Fakten gut modelliert zu sein, z. B. als ein Spieler rekrutiert, verletzt, in den Ruhestand versetzt wurde usw. Offensichtlich ist die aktuelle Spielerliste eine wichtige Tatsache, die wahrscheinlich im Vordergrund stehen sollte. Mitte, aber es kann andere interessante Dinge geben, die Sie mit diesem Objekt tun möchten, die eine historischere Perspektive erfordern.
quelle
IEnumerable<T>
- eine Sequenz ?Wow, dein Beitrag enthält eine ganze Reihe von Fragen und Punkten. Die meisten Argumente, die Sie von Microsoft erhalten, sind genau richtig. Beginnen wir mit allem
List<T>
List<T>
ist stark optimiert. Die Hauptverwendung besteht darin, als privates Mitglied eines Objekts verwendet zu werden.class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }
. Jetzt ist es so einfach wie zu tunvar list = new MyList<int, string>();
.IList<T>
wenn Sie einen Verbraucher benötigen, der über eine indizierte Liste verfügt. Auf diese Weise können Sie die Implementierung innerhalb einer Klasse später ändern.Collection<T>
sehr allgemein gemacht, weil es ein generisches Konzept ist ... der Name sagt alles; Es ist nur eine Sammlung. Es gibt präzisere Versionen wieSortedCollection<T>
,ObservableCollection<T>
,ReadOnlyCollection<T>
usw. , von denen jeder implementierenIList<T>
aber nichtList<T>
.Collection<T>
Ermöglicht das Überschreiben von Mitgliedern (z. B. Hinzufügen, Entfernen usw.), da diese virtuell sind.List<T>
nicht.Wenn ich diesen Code schreiben würde, würde die Klasse wahrscheinlich ungefähr so aussehen:
quelle
List<T>
es als private innere Klasse erweitert, die Generika verwendet, um die Verwendung und Deklaration von zu vereinfachenList<T>
. Wenn die Erweiterung nur wäreclass FootballRoster : List<FootballPlayer> { }
, hätte ich stattdessen einen Alias verwenden können. Ich denke, es ist erwähnenswert, dass Sie zusätzliche Mitglieder / Funktionen hinzufügen können, aber es ist begrenzt, da Sie wichtige Mitglieder von nicht überschreiben könnenList<T>
.Collection<T> allows for members (i.e. Add, Remove, etc.) to be overridden
Nun , dass ist ein guter Grund , nicht zu vererben Liste. Die anderen Antworten haben viel zu viel Philosophie.Vorheriger Code bedeutet: Ein paar Leute von der Straße, die Fußball spielen, und sie haben zufällig einen Namen. Etwas wie:
Wie auch immer, dieser Code (aus meiner Antwort)
Mittel: Dies ist eine Fußballmannschaft mit Management, Spielern, Admins usw. So etwas wie:
So wird Ihre Logik in Bildern dargestellt…
quelle
private readonly List<T> _roster = new List<T>(44);
System.Linq
Namespace zu importieren .Dies ist ein klassisches Beispiel für Komposition vs. Vererbung .
In diesem speziellen Fall:
Ist das Team eine Liste von Spielern mit zusätzlichem Verhalten?
oder
Ist das Team ein eigenes Objekt, das zufällig eine Liste von Spielern enthält?
Durch die Erweiterung von List beschränken Sie sich auf verschiedene Weise:
Sie können den Zugriff nicht einschränken (z. B. verhindern, dass Personen den Dienstplan ändern). Sie erhalten alle List-Methoden, unabhängig davon, ob Sie sie alle benötigen / möchten oder nicht.
Was passiert, wenn Sie auch Listen anderer Dinge haben möchten? Zum Beispiel haben Teams Trainer, Manager, Fans, Ausrüstung usw. Einige davon sind möglicherweise eigenständige Listen.
Sie beschränken Ihre Vererbungsoptionen. Beispielsweise möchten Sie möglicherweise ein generisches Teamobjekt erstellen und dann BaseballTeam, FootballTeam usw. verwenden, die davon erben. Um von List zu erben, müssen Sie die Vererbung von Team durchführen. Dies bedeutet jedoch, dass alle verschiedenen Teamtypen gezwungen sind, dieselbe Implementierung dieses Dienstplans durchzuführen.
Zusammensetzung - einschließlich eines Objekts, das das gewünschte Verhalten in Ihrem Objekt angibt.
Vererbung - Ihr Objekt wird zu einer Instanz des Objekts mit dem gewünschten Verhalten.
Beide haben ihre Verwendung, aber dies ist ein klarer Fall, in dem die Zusammensetzung vorzuziehen ist.
quelle
Wie jeder betont hat, ist ein Team von Spielern keine Liste von Spielern. Dieser Fehler wird von vielen Menschen überall gemacht, vielleicht auf verschiedenen Ebenen des Fachwissens. Oft ist das Problem subtil und gelegentlich sehr grob, wie in diesem Fall. Solche Designs sind schlecht, weil sie gegen das Liskov-Substitutionsprinzip verstoßen . Das Internet hat viele gute Artikel, die dieses Konzept erklären, z. B. http://en.wikipedia.org/wiki/Liskov_substitution_principle
Zusammenfassend gibt es zwei Regeln, die in einer Eltern / Kind-Beziehung zwischen Klassen beibehalten werden müssen:
Mit anderen Worten, ein Elternteil ist eine notwendige Definition eines Kindes, und ein Kind ist eine ausreichende Definition eines Elternteils.
Hier ist eine Möglichkeit, die eigene Lösung zu überdenken und das oben genannte Prinzip anzuwenden, um einen solchen Fehler zu vermeiden. Man sollte seine Hypothese testen, indem man überprüft, ob alle Operationen einer übergeordneten Klasse sowohl strukturell als auch semantisch für die abgeleitete Klasse gültig sind.
Wie Sie sehen, gilt nur das erste Merkmal einer Liste für ein Team. Ein Team ist also keine Liste. Eine Liste ist ein Implementierungsdetail für die Verwaltung Ihres Teams. Sie sollte daher nur zum Speichern der Spielerobjekte und zum Bearbeiten mit Methoden der Teamklasse verwendet werden.
An dieser Stelle möchte ich darauf hinweisen, dass eine Teamklasse meiner Meinung nach nicht einmal mithilfe einer Liste implementiert werden sollte. In den meisten Fällen sollte es mit einer Set-Datenstruktur (z. B. HashSet) implementiert werden.
quelle
Was ist, wenn das
FootballTeam
Reserveteam zusammen mit dem Hauptteam hat?Wie würden Sie das modellieren?
Die Beziehung ist eindeutig hat ein und nicht ist ein .
oder
RetiredPlayers
?Als Faustregel gilt: Wenn Sie jemals von einer Sammlung erben möchten, benennen Sie die Klasse
SomethingCollection
.Tut dein
SomethingCollection
semantisch sinnvoll? Tun Sie dies nur, wenn Ihr Typ eine Sammlung von istSomething
.Im Falle des
FootballTeam
klingt es nicht richtig. ATeam
ist mehr als aCollection
. EINTeam
kann Trainer, Trainer usw. haben, wie die anderen Antworten gezeigt haben.FootballCollection
klingt wie eine Sammlung von Fußbällen oder vielleicht eine Sammlung von Fußballutensilien.TeamCollection
, eine Sammlung von Teams.FootballPlayerCollection
klingt wie eine Sammlung von Spielern, die ein gültiger Name für eine Klasse wäre, von der erbtList<FootballPlayer>
wenn Sie das wirklich wollten.Ja wirklich
List<FootballPlayer>
ist ein perfekter Typ, um damit umzugehen. Könnte seinIList<FootballPlayer>
wenn Sie es von einer Methode zurückgeben.Zusammenfassend
Frag dich selbst
Ist
X
einY
? oder hatX
einY
?Bedeuten meine Klassennamen, was sie sind?
quelle
DefensivePlayers
,OffensivePlayers
oderOtherPlayers
es legitim nützlich sein könnte , eine Art zu haben , die von Code verwendet werden könnten , die eine erwartet ,List<Player>
sondern auch enthalten MitgliederDefensivePlayers
,OffsensivePlayers
oderSpecialPlayers
der TypIList<DefensivePlayer>
,IList<OffensivePlayer>
undIList<Player>
. Man könnte ein separates Objekt verwenden, um die separaten Listen zwischenzuspeichern, aber es würde sauberer erscheinen, sie in demselben Objekt wie die Hauptliste zu kapseln [verwenden Sie die Ungültigmachung eines Listenaufzählers ...Design> Implementierung
Welche Methoden und Eigenschaften Sie verfügbar machen, ist eine Entwurfsentscheidung. Welche Basisklasse Sie erben, ist ein Implementierungsdetail. Ich denke, es lohnt sich, einen Schritt zurück zu ersteren zu machen.
Ein Objekt ist eine Sammlung von Daten und Verhalten.
Ihre ersten Fragen sollten also lauten:
Denken Sie daran, dass Vererbung eine "isa" (ist eine) Beziehung impliziert, während Komposition eine "hat eine" (hasa) Beziehung impliziert. Wählen Sie aus Ihrer Sicht die richtige für Ihre Situation aus, und berücksichtigen Sie dabei, wohin die Entwicklung Ihrer Anwendung führen kann.
Denken Sie an Schnittstellen, bevor Sie an konkrete Typen denken, da es für manche Menschen einfacher ist, ihr Gehirn auf diese Weise in den "Entwurfsmodus" zu versetzen.
Dies ist nicht etwas, was jeder auf dieser Ebene bei der täglichen Codierung bewusst tut. Aber wenn Sie über diese Art von Thema nachdenken, treten Sie in Designgewässern auf. Sich dessen bewusst zu sein, kann befreiend sein.
Design-Besonderheiten berücksichtigen
Sehen Sie sich List <T> und IList <T> in MSDN oder Visual Studio an. Sehen Sie, welche Methoden und Eigenschaften sie verfügbar machen. Sehen diese Methoden aus Ihrer Sicht alle so aus, als würde jemand einem FootballTeam etwas antun wollen?
Ist FootballTeam.Reverse () für Sie sinnvoll? Sieht FootballTeam.ConvertAll <TOutput> () wie etwas aus, das Sie wollen?
Dies ist keine Trickfrage; Die Antwort könnte wirklich "Ja" sein. Wenn Sie List <Player> oder IList <Player> implementieren / erben, bleiben Sie dabei. Wenn das für Ihr Modell ideal ist, tun Sie es.
Wenn Sie sich für Ja entscheiden, ist dies sinnvoll, und Sie möchten, dass Ihr Objekt als Sammlung / Liste von Spielern (Verhalten) behandelt werden kann, und Sie möchten daher auf jeden Fall ICollection oder IList implementieren. Nominal:
Wenn Sie möchten, dass Ihr Objekt eine Sammlung / Liste von Spielern (Daten) enthält und Sie daher möchten, dass die Sammlung oder Liste eine Eigenschaft oder ein Mitglied ist, tun Sie dies auf jeden Fall. Nominal:
Möglicherweise möchten Sie, dass die Benutzer nur die Gruppe der Spieler aufzählen können, anstatt sie zu zählen, zu ihnen hinzuzufügen oder zu entfernen. IEnumerable <Player> ist eine absolut gültige Option.
Möglicherweise haben Sie das Gefühl, dass keine dieser Schnittstellen in Ihrem Modell überhaupt nützlich ist. Dies ist weniger wahrscheinlich (IEnumerable <T> ist in vielen Situationen nützlich), aber es ist immer noch möglich.
Jeder, der versucht, Ihnen zu sagen, dass eines davon in jedem Fall kategorisch und definitiv falsch ist, ist irregeführt. Jeder, der versucht, Ihnen zu sagen, dass es in jedem Fall kategorisch und definitiv richtig ist, ist irregeführt.
Fahren Sie mit der Implementierung fort
Sobald Sie sich für Daten und Verhalten entschieden haben, können Sie eine Entscheidung über die Implementierung treffen. Dies schließt ein, von welchen konkreten Klassen Sie über Vererbung oder Zusammensetzung abhängig sind.
Dies ist möglicherweise kein großer Schritt, und die Leute verbinden häufig Design und Implementierung, da es durchaus möglich ist, in ein oder zwei Sekunden alles in Ihrem Kopf durchzugehen und mit dem Tippen zu beginnen.
Ein Gedankenexperiment
Ein künstliches Beispiel: Wie andere bereits erwähnt haben, ist ein Team nicht immer "nur" eine Ansammlung von Spielern. Führen Sie eine Sammlung von Spielergebnissen für die Mannschaft? Ist die Mannschaft in Ihrem Modell mit dem Verein austauschbar? Wenn ja, und wenn Ihr Team eine Sammlung von Spielern ist, ist es vielleicht auch eine Sammlung von Mitarbeitern und / oder eine Sammlung von Punktzahlen. Dann haben Sie am Ende:
Ungeachtet des Designs können Sie zu diesem Zeitpunkt in C # ohnehin nicht alle diese Funktionen implementieren, indem Sie von List <T> erben, da C # "nur" die Einzelvererbung unterstützt. (Wenn Sie diese Malarkie in C ++ ausprobiert haben, können Sie dies als eine gute Sache betrachten.) Das Implementieren einer Sammlung über Vererbung und einer über Komposition fühlt sich wahrscheinlich schmutzig an. Und Eigenschaften wie Count werden für Benutzer verwirrend, es sei denn, Sie implementieren ILIst <Player> .Count und IList <StaffMember> .Count usw. explizit, und dann sind sie eher schmerzhaft als verwirrend. Sie können sehen, wohin das führt; Das Bauchgefühl beim Nachdenken über diese Straße kann Ihnen durchaus sagen, dass es sich falsch anfühlt, in diese Richtung zu gehen (und zu Recht oder zu Unrecht könnten Ihre Kollegen dies auch tun, wenn Sie es auf diese Weise umgesetzt haben!).
Die kurze Antwort (zu spät)
Die Richtlinie, nicht von Sammlungsklassen zu erben, ist nicht C # -spezifisch. Sie finden sie in vielen Programmiersprachen. Es wird Weisheit empfangen, kein Gesetz. Ein Grund dafür ist, dass in der Praxis die Komposition in Bezug auf Verständlichkeit, Implementierbarkeit und Wartbarkeit häufig die Vererbung gewinnt. Bei Objekten aus der realen Welt / Domäne ist es üblicher, nützliche und konsistente "hasa" -Beziehungen zu finden als nützliche und konsistente "isa" -Beziehungen, es sei denn, Sie sind tief in der Zusammenfassung verwurzelt, insbesondere im Laufe der Zeit und der genauen Daten und des Verhaltens von Objekten im Code Änderungen. Dies sollte nicht dazu führen, dass Sie das Erben von Auflistungsklassen immer ausschließen. aber es kann suggestiv sein.
quelle
Zuallererst hat es mit Usability zu tun. Wenn Sie die Vererbung verwenden, macht die
Team
Klasse Verhalten (Methoden) verfügbar, die ausschließlich für die Objektmanipulation ausgelegt sind. Zum BeispielAsReadOnly()
oderCopyTo(obj)
Methoden machen für das Teamobjekt keinen Sinn. Anstelle derAddRange(items)
Methode möchten Sie wahrscheinlich eine aussagekräftigereAddPlayers(players)
Methode.Wenn Sie LINQ verwenden möchten, implementieren Sie eine generische Schnittstelle wie
ICollection<T>
oderIEnumerable<T>
sinnvoller.Wie bereits erwähnt, ist Komposition der richtige Weg. Implementieren Sie einfach eine Liste von Spielern als private Variable.
quelle
Eine Fußballmannschaft ist keine Liste von Fußballspielern. Eine Fußballmannschaft besteht aus einer Liste von Fußballspielern!
Das ist logisch falsch:
und das ist richtig:
quelle
Das hängt vom Kontext ab
Wenn Sie Ihre Mannschaft als eine Liste von Spielern betrachten, projizieren Sie die "Idee" einer Fußballmannschaft auf einen Aspekt: Sie reduzieren die "Mannschaft" auf die Personen, die Sie auf dem Spielfeld sehen. Diese Projektion ist nur in einem bestimmten Kontext korrekt. In einem anderen Kontext könnte dies völlig falsch sein. Stellen Sie sich vor, Sie möchten Sponsor des Teams werden. Sie müssen also mit den Managern des Teams sprechen. In diesem Zusammenhang wird das Team auf die Liste seiner Manager projiziert. Und diese beiden Listen überschneiden sich normalerweise nicht sehr. Andere Kontexte sind die aktuellen gegen die früheren Spieler usw.
Unklare Semantik
Das Problem bei der Betrachtung eines Teams als Liste seiner Spieler besteht darin, dass seine Semantik vom Kontext abhängt und nicht erweitert werden kann, wenn sich der Kontext ändert. Außerdem ist es schwer auszudrücken, welchen Kontext Sie verwenden.
Klassen sind erweiterbar
Wenn Sie eine Klasse mit nur einem Mitglied verwenden (z
IList activePlayers
) verwenden, können Sie den Namen des Mitglieds (und zusätzlich seinen Kommentar) verwenden, um den Kontext zu verdeutlichen. Wenn es zusätzliche Kontexte gibt, fügen Sie einfach ein zusätzliches Mitglied hinzu.Klassen sind komplexer
In einigen Fällen kann es übertrieben sein, eine zusätzliche Klasse zu erstellen. Jede Klassendefinition muss über den Klassenladeprogramm geladen werden und wird von der virtuellen Maschine zwischengespeichert. Dies kostet Sie Laufzeitleistung und Speicher. Wenn Sie einen ganz bestimmten Kontext haben, ist es möglicherweise in Ordnung, eine Fußballmannschaft als Liste von Spielern zu betrachten. Aber in diesem Fall sollten Sie wirklich nur a verwenden
IList
, keine daraus abgeleitete Klasse verwenden.Schlussfolgerung / Überlegungen
Wenn Sie einen ganz bestimmten Kontext haben, ist es in Ordnung, ein Team als Liste von Spielern zu betrachten. Zum Beispiel ist es innerhalb einer Methode völlig in Ordnung zu schreiben:
Bei Verwendung von F # kann es sogar in Ordnung sein, eine Typabkürzung zu erstellen:
Wenn der Kontext jedoch breiter oder sogar unklar ist, sollten Sie dies nicht tun. Dies ist insbesondere dann der Fall, wenn Sie eine neue Klasse erstellen, deren Kontext, in dem sie möglicherweise in Zukunft verwendet wird, nicht klar ist. Ein Warnzeichen ist, wenn Sie Ihrer Klasse zusätzliche Attribute hinzufügen (Name der Mannschaft, des Trainers usw.). Dies ist ein klares Zeichen dafür, dass der Kontext, in dem die Klasse verwendet wird, nicht festgelegt ist und sich in Zukunft ändern wird. In diesem Fall können Sie das Team nicht als Liste von Spielern betrachten, aber Sie sollten die Liste der (derzeit aktiven, nicht verletzten usw.) Spieler als Attribut des Teams modellieren.
quelle
Lassen Sie mich Ihre Frage umschreiben. Sie können das Thema also aus einer anderen Perspektive betrachten.
Wenn ich eine Fußballmannschaft vertreten muss, verstehe ich, dass es im Grunde ein Name ist. Wie: "Die Adler"
Später wurde mir klar, dass Teams auch Spieler haben.
Warum kann ich den Stringtyp nicht einfach so erweitern, dass er auch eine Liste von Spielern enthält?
Ihr Einstieg in das Problem ist willkürlich. Versuchen Sie zu überlegen, was ein Team hat (Eigenschaften), nicht was es ist .
Nachdem Sie dies getan haben, können Sie sehen, ob es Eigenschaften mit anderen Klassen teilt. Und denken Sie an Vererbung.
quelle
Nur weil ich denke, dass die anderen Antworten ziemlich stark davon abhängen, ob eine Fußballmannschaft "ein"
List<FootballPlayer>
oder "ein" istList<FootballPlayer>
, was diese Frage wirklich nicht wie geschrieben beantwortet.Das OP bittet hauptsächlich um Klarstellung der Richtlinien für das Erben von
List<T>
:Da
List<T>
hat keine virtuellen Methoden. Dies ist in Ihrem eigenen Code weniger problematisch, da Sie die Implementierung normalerweise mit relativ geringem Aufwand austauschen können - in einer öffentlichen API kann dies jedoch ein viel größeres Problem sein.Eine öffentliche API ist eine Schnittstelle, die Sie Programmierern von Drittanbietern zur Verfügung stellen. Denken Sie an Framework-Code. Denken Sie daran, dass es sich bei den Richtlinien, auf die verwiesen wird, um die ".NET Framework- Entwurfsrichtlinien" und nicht um die ".NET Application Design-Richtlinien" handelt. Es gibt einen Unterschied, und im Allgemeinen ist das öffentliche API-Design viel strenger.
Ziemlich genau, ja. Möglicherweise möchten Sie die Gründe dafür berücksichtigen, um festzustellen, ob sie ohnehin für Ihre Situation zutreffen. Wenn Sie jedoch keine öffentliche API erstellen, müssen Sie sich nicht besonders um API-Probleme wie die Versionierung kümmern (von denen dies eine ist Teilmenge).
Wenn Sie in Zukunft eine öffentliche API hinzufügen, müssen Sie entweder Ihre API von Ihrer Implementierung abstrahieren (indem Sie Ihre nicht
List<T>
direkt offenlegen ) oder die Richtlinien mit den möglichen zukünftigen Schmerzen verletzen, die damit verbunden sind.Hängt vom Kontext ab, aber da wir dies
FootballTeam
als Beispiel verwenden - stellen Sie sich vor, Sie können kein hinzufügen,FootballPlayer
wenn dies dazu führen würde, dass das Team die Gehaltsobergrenze überschreitet. Ein möglicher Weg, dies hinzuzufügen, wäre etwa:Ah ... aber Sie können nicht,
override Add
weil es nicht istvirtual
(aus Leistungsgründen).Wenn Sie sich in einer Anwendung befinden (was im Grunde bedeutet, dass Sie und alle Ihre Anrufer zusammen kompiliert werden), können Sie jetzt
IList<T>
alle Kompilierungsfehler verwenden und beheben:Wenn Sie jedoch öffentlich einem Drittanbieter ausgesetzt waren, haben Sie gerade eine wichtige Änderung vorgenommen, die zu Kompilierungs- und / oder Laufzeitfehlern führt.
TL; DR - Die Richtlinien gelten für öffentliche APIs. Machen Sie für private APIs, was Sie wollen.
quelle
Erlaubt es den Leuten zu sagen
überhaupt einen Sinn ergeben? Wenn nicht, sollte es keine Liste sein.
quelle
myTeam.subTeam(3, 5);
subList
wird nicht mal mehr ein zurückkehrenTeam
.Dies hängt vom Verhalten Ihres "Team" -Objekts ab. Wenn es sich wie eine Sammlung verhält, ist es möglicherweise in Ordnung, es zuerst mit einer einfachen Liste darzustellen. Dann werden Sie möglicherweise feststellen, dass Sie weiterhin Code duplizieren, der in der Liste wiederholt wird. Zu diesem Zeitpunkt haben Sie die Möglichkeit, ein FootballTeam-Objekt zu erstellen, das die Liste der Spieler umschließt. Die FootballTeam-Klasse wird zur Heimat des gesamten Codes, der auf der Liste der Spieler wiederholt wird.
Verkapselung. Ihre Kunden müssen nicht wissen, was in FootballTeam vor sich geht. Wie alle Ihre Kunden wissen, kann dies implementiert werden, indem Sie die Liste der Spieler in einer Datenbank nachschlagen. Sie müssen es nicht wissen, und dies verbessert Ihr Design.
Genau :) Sie werden sagen, FootballTeam.Add (John), nicht FootballTeam.List.Add (John). Die interne Liste ist nicht sichtbar.
quelle
Hier gibt es viele hervorragende Antworten, aber ich möchte auf etwas eingehen, das ich nicht erwähnt habe: Bei objektorientiertem Design geht es darum , Objekte zu stärken .
Sie möchten alle Ihre Regeln, zusätzlichen Arbeiten und internen Details in einem geeigneten Objekt zusammenfassen. Auf diese Weise müssen sich andere Objekte, die mit diesem interagieren, nicht um alles kümmern. Sie möchten sogar noch einen Schritt weiter gehen und aktiv verhindern, dass andere Objekte diese Interna umgehen.
Wenn Sie von erben
List
, können alle anderen Objekte Sie als Liste sehen. Sie haben direkten Zugriff auf die Methoden zum Hinzufügen und Entfernen von Spielern. Und du hast deine Kontrolle verloren; zum Beispiel:Angenommen, Sie möchten unterscheiden, wann ein Spieler abreist, indem Sie wissen, ob er in den Ruhestand getreten, zurückgetreten oder entlassen wurde. Sie können eine
RemovePlayer
Methode implementieren , die eine entsprechende Eingabeaufzählung verwendet. Jedoch durch Erben vonList
, würden Sie nicht in der Lage einen direkten Zugang zu verhindernRemove
,RemoveAll
und sogarClear
. Infolgedessen haben Sie Ihre Klasse tatsächlich entmachtetFootballTeam
.Zusätzliche Gedanken zur Kapselung ... Sie haben folgende Bedenken geäußert:
Sie haben Recht, das wäre unnötig ausführlich für alle Kunden, um Ihr Team zu nutzen. Dieses Problem ist jedoch im Vergleich zu der Tatsache, dass Sie
List Players
allen und jedem ausgesetzt waren , sehr gering, sodass sie ohne Ihre Zustimmung mit Ihrem Team herumspielen können.Sie sagen weiter:
Beim ersten Punkt liegen Sie falsch: Lassen Sie das Wort "Liste" fallen, und es ist tatsächlich offensichtlich, dass ein Team Spieler hat.
Sie treffen jedoch mit der Sekunde den Nagel auf den Kopf. Sie möchten nicht, dass Kunden anrufen
ateam.Players.Add(...)
. Sie wollen, dass sie anrufenateam.AddPlayer(...)
. Und Ihre Implementierung würde (möglicherweise unter anderem)Players.Add(...)
intern aufgerufen .Hoffentlich können Sie sehen, wie wichtig die Kapselung für das Ziel ist, Ihre Objekte zu stärken. Sie möchten, dass jede Klasse ihre Arbeit gut erledigt, ohne Angst vor Störungen durch andere Objekte zu haben.
quelle
Denken Sie daran: "Alle Modelle sind falsch, aber einige sind nützlich." - George EP Box
Es gibt keinen "richtigen Weg", nur einen nützlichen.
Wählen Sie eine, die für Sie und / oder Ihre Benutzer nützlich ist. Das ist es.
Wirtschaftlich entwickeln, nicht überentwickeln. Je weniger Code Sie schreiben, desto weniger Code müssen Sie debuggen.(Lesen Sie die folgenden Ausgaben).- Bearbeitet
Meine beste Antwort wäre ... es kommt darauf an. Das Erben von einer Liste würde die Clients dieser Klasse Methoden aussetzen, die möglicherweise nicht verfügbar gemacht werden sollten, vor allem, weil FootballTeam wie eine Geschäftseinheit aussieht.
- Ausgabe 2
Ich erinnere mich aufrichtig nicht an das, worauf ich mich in dem Kommentar „Nicht überentwickeln“ bezog. Obwohl ich der Meinung bin, dass die KISS- Denkweise ein guter Leitfaden ist, möchte ich betonen, dass das Erben einer Business-Klasse von List aufgrund von Abstraktionsverlusten mehr Probleme verursachen als lösen würde .
Andererseits glaube ich, dass es eine begrenzte Anzahl von Fällen gibt, in denen es nützlich ist, einfach von List zu erben. Wie ich in der vorherigen Ausgabe geschrieben habe, kommt es darauf an. Die Antwort auf jeden Fall wird stark von Wissen, Erfahrung und persönlichen Vorlieben beeinflusst.
Vielen Dank an @kai, der mir geholfen hat, genauer über die Antwort nachzudenken.
quelle
List
würde die Clients dieser Klasse Methoden aussetzen, die möglicherweise nicht verfügbar gemacht werden sollten " ist genau das, was das Erben vonList
einem absoluten Nein-Nein ausmacht . Sobald sie freigelegt sind, werden sie im Laufe der Zeit allmählich missbraucht und missbraucht. Zeitersparnis durch "wirtschaftliche Entwicklung" kann leicht zu einer Verzehnfachung der in Zukunft verlorenen Einsparungen führen: Debuggen der Missbräuche und eventuelles Refactoring, um die Vererbung zu beheben. Das YAGNI-Prinzip kann auch als Bedeutung angesehen werden: Sie werden nicht alle diese MethodenList
benötigen, also legen Sie sie nicht offen.List
weil es schnell und einfach ist und somit zu weniger Fehlern führen würde, ist einfach falsch. Es ist nur schlechtes Design und schlechtes Design führt zu viel mehr Fehlern als "Over Engineering".Dies erinnert mich an das "Ist ein" gegen "hat einen" Kompromiss. Manchmal ist es einfacher und sinnvoller, direkt von einer Superklasse zu erben. In anderen Fällen ist es sinnvoller, eine eigenständige Klasse zu erstellen und die Klasse, von der Sie geerbt hätten, als Mitgliedsvariable einzuschließen. Sie können weiterhin auf die Funktionalität der Klasse zugreifen, sind jedoch nicht an die Schnittstelle oder andere Einschränkungen gebunden, die möglicherweise durch das Erben von der Klasse entstehen.
Was machst du Wie bei vielen Dingen ... hängt es vom Kontext ab. Die Anleitung, die ich verwenden würde, ist, dass es wirklich eine "ist eine" Beziehung geben sollte, um von einer anderen Klasse zu erben. Wenn Sie also eine Klasse namens BMW schreiben, könnte sie von Car erben, weil ein BMW wirklich ein Auto ist. Eine Pferdeklasse kann von der Säugetierklasse erben, da ein Pferd im wirklichen Leben tatsächlich ein Säugetier ist und jede Säugetierfunktionalität für Pferd relevant sein sollte. Aber können Sie sagen, dass ein Team eine Liste ist? Soweit ich das beurteilen kann, scheint es nicht so, als ob ein Team wirklich eine Liste ist. In diesem Fall hätte ich also eine Liste als Mitgliedsvariable.
quelle
Mein schmutziges Geheimnis: Es ist mir egal, was die Leute sagen, und ich mache es. .NET Framework wird mit "XxxxCollection" (UIElementCollection für das beste Beispiel) verbreitet.
Was hält mich davon ab zu sagen:
Wenn ich es besser finde als
Darüber hinaus kann meine PlayerCollection von anderen Klassen wie "Club" ohne Code-Duplizierung verwendet werden.
Best Practices von gestern sind möglicherweise nicht die von morgen. Es gibt keinen Grund für die meisten Best Practices, die meisten sind sich nur in der Community einig. Anstatt die Community zu fragen, ob sie Ihnen dabei die Schuld geben wird, fragen Sie sich, was ist besser lesbar und wartbar?
oder
Ja wirklich. Hast du irgendwelche Zweifel? Vielleicht müssen Sie jetzt mit anderen technischen Einschränkungen spielen, die Sie daran hindern, List <T> in Ihrem realen Anwendungsfall zu verwenden. Fügen Sie jedoch keine Einschränkung hinzu, die nicht vorhanden sein sollte. Wenn Microsoft das Warum nicht dokumentiert hat, dann ist es sicherlich eine "Best Practice", die aus dem Nichts kommt.
quelle
Collection<T>
ist nicht dasselbe wieList<T>
so, dass es möglicherweise einige Arbeit erfordert, um in einem großen Projekt zu überprüfen.team.ByName("Nicolas")
Mittel"Nicolas"
ist der Name des Teams.Die Richtlinien besagen, dass die öffentliche API nicht die interne Entwurfsentscheidung darüber offenlegen sollte, ob Sie eine Liste, einen Satz, ein Wörterbuch, einen Baum oder was auch immer verwenden. Ein "Team" ist nicht unbedingt eine Liste. Sie können es als Liste implementieren, aber Benutzer Ihrer öffentlichen API sollten Ihre Klasse nach Bedarf verwenden. Auf diese Weise können Sie Ihre Entscheidung ändern und eine andere Datenstruktur verwenden, ohne die öffentliche Schnittstelle zu beeinträchtigen.
quelle
class FootballTeam : List<FootballPlayers>
, können Benutzer meiner Klasse in diesem Fall sagen, dass ich ' habe geerbt vonList<T>
durch Reflexion, sieht die Verwendung vonList<T>
Methoden, die keinen Sinn für eine Fußballmannschaft machen, und in der Lage sein zu verwenden ,FootballTeam
inList<T>
, also würde ich an den Client aufschlussImplementierungsDetails wird (unnötig).Wenn sie sagen
List<T>
, dass es "optimiert" ist, wollen sie damit meinen, dass es keine Funktionen wie virtuelle Methoden gibt, die etwas teurer sind. Das Problem ist also, dass Sie, sobald SieList<T>
in Ihrer öffentlichen API verfügbar gemacht haben , nicht mehr in der Lage sind, Geschäftsregeln durchzusetzen oder deren Funktionalität später anzupassen. Wenn Sie diese geerbte Klasse jedoch als intern in Ihrem Projekt verwenden (im Gegensatz dazu, dass sie möglicherweise Tausenden Ihrer Kunden / Partner / anderen Teams als API ausgesetzt ist), ist es möglicherweise in Ordnung, wenn dies Ihre Zeit spart und die gewünschte Funktionalität bietet Duplikat. Der Vorteil des Erbens von für die Lebensdauer Ihrer APIs kann dann auch in Ordnung sein.List<T>
besteht darin, dass Sie viel dummen Wrapper-Code eliminieren, der in absehbarer Zukunft niemals angepasst werden wird. Auch wenn Sie möchten, dass Ihre Klasse explizit genau dieselbe Semantik wie hatList<T>
Ich sehe oft viele Leute, die tonnenweise zusätzliche Arbeit leisten, nur weil die FxCop-Regel dies sagt oder wenn jemandes Blog sagt, dass es eine "schlechte" Praxis ist. Oft wird dadurch Code zum Entwerfen von Palooza-Verrücktheiten. Behandeln Sie es wie viele Richtlinien als Richtlinie, die Ausnahmen haben kann .
quelle
Obwohl ich nicht wie die meisten dieser Antworten einen komplexen Vergleich habe, möchte ich meine Methode zur Behandlung dieser Situation mitteilen. Durch die Erweiterung
IEnumerable<T>
können Sie IhrerTeam
Klasse erlauben , Linq-Abfrageerweiterungen zu unterstützen, ohne alle Methoden und Eigenschaften von öffentlich zugänglich zu machenList<T>
.quelle
Wenn Ihre Klassenbenutzer alle Methoden und Eigenschaften benötigen, die ** List hat, sollten Sie Ihre Klasse daraus ableiten. Wenn sie diese nicht benötigen, fügen Sie die Liste bei und erstellen Sie Wrapper für Methoden, die Ihre Klassenbenutzer tatsächlich benötigen.
Dies ist eine strenge Regel, wenn Sie eine öffentliche API oder einen anderen Code schreiben , der von vielen Personen verwendet wird. Sie können diese Regel ignorieren, wenn Sie eine winzige App und nicht mehr als 2 Entwickler haben. Dies spart Ihnen Zeit.
Bei kleinen Apps können Sie auch eine andere, weniger strenge Sprache wählen. Ruby, JavaScript - alles, mit dem Sie weniger Code schreiben können.
quelle
Ich wollte nur hinzufügen, dass Bertrand Meyer, der Erfinder von Eiffel und Design by Contract, von ihm
Team
geerbt hätte,List<Player>
ohne auch nur ein Augenlid zu schlagen.In seinem Buch " Objektorientierte Softwarekonstruktion" beschreibt er die Implementierung eines GUI-Systems, bei dem rechteckige Fenster untergeordnete Fenster haben können. Er hat einfach
Window
von beiden erbenRectangle
undTree<Window>
um die Umsetzung wiederverwendet werden .C # ist jedoch nicht Eiffel. Letzteres unterstützt die Mehrfachvererbung und Umbenennung von Features . Wenn Sie in C # eine Unterklasse erstellen, erben Sie sowohl die Schnittstelle als auch die Implementierung. Sie können die Implementierung überschreiben, aber die Aufrufkonventionen werden direkt aus der Oberklasse kopiert. In Eiffel, jedoch können Sie die Namen der öffentlichen Methoden ändern, so dass Sie umbenennen
Add
undRemove
aufHire
undFire
in IhremTeam
. Wenn eine Instanz vonTeam
zurückgesendetList<Player>
wird, verwendetAdd
undRemove
ändert der Anrufer sie, aber Ihre virtuellen MethodenHire
undFire
werden aufgerufen.quelle
Ich glaube, ich stimme Ihrer Verallgemeinerung nicht zu. Ein Team ist nicht nur eine Ansammlung von Spielern. Ein Team hat so viel mehr Informationen darüber - Name, Emblem, Sammlung von Management- / Verwaltungsmitarbeitern, Sammlung von Trainerteams, dann Sammlung von Spielern. Richtig, Ihre FootballTeam-Klasse sollte 3 Sammlungen haben und selbst keine Sammlung sein. wenn es darum geht, die reale Welt richtig zu modellieren.
Sie könnten eine PlayerCollection-Klasse in Betracht ziehen, die wie die Specialized StringCollection einige andere Funktionen bietet - wie Validierung und Überprüfungen, bevor Objekte zum internen Speicher hinzugefügt oder aus diesem entfernt werden.
Vielleicht passt die Vorstellung einer PlayerCollection besser zu Ihrem bevorzugten Ansatz?
Und dann kann das FootballTeam so aussehen:
quelle
Bevorzugen Sie Schnittstellen gegenüber Klassen
Klassen sollten vermeiden, von Klassen abgeleitet zu werden, und stattdessen die erforderlichen minimalen Schnittstellen implementieren.
Vererbung bricht Kapselung
Das Ableiten von Klassen bricht die Kapselung ab :
Dies erschwert unter anderem die Umgestaltung Ihres Codes.
Klassen sind ein Implementierungsdetail
Klassen sind ein Implementierungsdetail, das vor anderen Teilen Ihres Codes verborgen bleiben sollte. Kurz gesagt, a
System.List
ist eine spezifische Implementierung eines abstrakten Datentyps, der jetzt und in Zukunft angemessen sein kann oder nicht.Konzeptionell ist die Tatsache, dass der
System.List
Datentyp "Liste" heißt, ein bisschen wie ein roter Hering. ASystem.List<T>
ist eine veränderbare geordnete Sammlung, die amortisierte O (1) -Operationen zum Hinzufügen, Einfügen und Entfernen von Elementen und O (1) -Operationen zum Abrufen der Anzahl von Elementen oder zum Abrufen und Festlegen von Elementen nach Index unterstützt.Je kleiner die Schnittstelle, desto flexibler der Code
Beim Entwerfen einer Datenstruktur ist der Code umso flexibler, je einfacher die Schnittstelle ist. Schauen Sie sich an, wie leistungsfähig LINQ ist, um dies zu demonstrieren.
So wählen Sie Schnittstellen aus
Wenn Sie an "Liste" denken, sollten Sie sich zunächst sagen: "Ich muss eine Sammlung von Baseballspielern repräsentieren." Nehmen wir also an, Sie möchten dies mit einer Klasse modellieren. Zunächst sollten Sie entscheiden, wie viele Schnittstellen diese Klasse mindestens verfügbar machen soll.
Einige Fragen, die diesen Prozess leiten können:
IEnumerable<T>
IReadonlyList<T>
.ICollection<T>
ISet<T>
?IList<T>
.Auf diese Weise koppeln Sie keine anderen Teile des Codes mit Implementierungsdetails Ihrer Baseballspieler-Sammlung und können die Implementierung ändern, solange Sie die Benutzeroberfläche respektieren.
Wenn Sie diesen Ansatz wählen, werden Sie feststellen, dass Code leichter zu lesen, umzugestalten und wiederzuverwenden ist.
Hinweise zum Vermeiden von Boilerplate
Die Implementierung von Schnittstellen in einer modernen IDE sollte einfach sein. Klicken Sie mit der rechten Maustaste und wählen Sie "Schnittstelle implementieren". Leiten Sie dann bei Bedarf alle Implementierungen an eine Mitgliedsklasse weiter.
Das heißt, wenn Sie feststellen, dass Sie viel Boilerplate schreiben, liegt dies möglicherweise daran, dass Sie mehr Funktionen verfügbar machen, als Sie sollten. Es ist der gleiche Grund, warum Sie nicht von einer Klasse erben sollten.
Sie können auch kleinere Schnittstellen entwerfen, die für Ihre Anwendung sinnvoll sind, und möglicherweise nur ein paar Hilfserweiterungsfunktionen, um diese Schnittstellen anderen zuzuordnen, die Sie benötigen. Dies ist der Ansatz, den ich in meiner eigenen
IArray
Schnittstelle für die LinqArray-Bibliothek gewählt habe .quelle
Probleme mit der Serialisierung
Ein Aspekt fehlt. Klassen, die von List <> erben können mit XmlSerializer nicht korrekt serialisiert werden . In diesem Fall muss stattdessen DataContractSerializer verwendet werden, oder es ist eine eigene Serialisierungsimplementierung erforderlich.
Weiterführende Literatur: Wenn eine Klasse von List <> geerbt wird, serialisiert XmlSerializer keine anderen Attribute
quelle
Um Eric Lippert zu zitieren:
Zum Beispiel haben Sie es satt, dass die
AddRange
Methode inIList<T>
folgenden Fällen fehlt :quelle