Ist es besser, List<string>
in Typanmerkungen oder StringList
wo zu verwendenStringList
class StringList : List<String> { /* no further code!*/ }
c#
inheritance
Ruudjah
quelle
quelle
StringList
undList<string>
? Es gibt keine, aber Sie (das generische "Sie") haben es geschafft, der Codebasis noch ein weiteres Detail hinzuzufügen, das nur für Ihr Projekt gilt und erst nach dem Studium des Codes für andere von Bedeutung ist. Warum den Namen einer Liste von Zeichenfolgen maskieren, um sie weiterhin als Liste von Zeichenfolgen zu bezeichnen? Auf der anderen Seite halteBagOBagels : List<string>
ich das für sinnvoller , wenn Sie das tun wollten . Sie können es als IEnumerable iterieren, externe Verbraucher interessieren sich nicht für die Implementierung - Güte alles in allem.Antworten:
Es gibt einen weiteren Grund, warum Sie möglicherweise von einem generischen Typ erben möchten.
Microsoft empfiehlt , das Verschachteln generischer Typen in Methodensignaturen zu vermeiden .
Es ist daher eine gute Idee, für einige Ihrer Generika Typen mit Geschäftsdomänennamen zu erstellen. Anstatt einen zu haben
IEnumerable<IDictionary<string, MyClass>>
, erstelle einen TypMyDictionary : IDictionary<string, MyClass>
und dann wird dein Mitglied einIEnumerable<MyDictionary>
, was viel besser lesbar ist. Außerdem können Sie MyDictionary an mehreren Stellen verwenden, um die Aussagekraft Ihres Codes zu erhöhen.Das hört sich vielleicht nicht nach einem großen Vorteil an, aber im realen Geschäftscode habe ich Generika gesehen, die Ihnen die Haare zu Berge stehen lassen. Dinge wie
List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>
. Die Bedeutung dieses Generikums ist in keiner Weise offensichtlich. Wenn es jedoch mit einer Reihe von explizit erstellten Typen erstellt würde, wäre die Absicht des ursprünglichen Entwicklers wahrscheinlich viel klarer. Wenn sich dieser generische Typ aus irgendeinem Grund ändern muss, kann es im Vergleich zu einer Änderung in der abgeleiteten Klasse ein echtes Problem sein, alle Instanzen dieses generischen Typs zu finden und zu ersetzen.Schließlich gibt es noch einen weiteren Grund, warum jemand seine eigenen Typen von Generika ableiten möchte. Betrachten Sie die folgenden Klassen:
ICollection<MyItems>
Woher wissen wir nun, was an den Konstruktor für MyConsumerClass übergeben werden soll? Wenn wir abgeleitete Klassenclass MyItems : ICollection<MyItems>
undclass MyItemCache : ICollection<MyItems>
erstellen, kann MyConsumerClass sehr genau festlegen, welche der beiden Sammlungen tatsächlich benötigt wird. Dies funktioniert dann sehr gut mit einem IoC-Container, der die beiden Sammlungen sehr leicht auflösen kann. Außerdem kann der Programmierer präziser arbeiten. Wenn es sinnvoll istMyConsumerClass
, mit einer ICollection zu arbeiten, kann er den generischen Konstruktor verwenden. Wenn es jedoch geschäftlich nie sinnvoll ist, einen der beiden abgeleiteten Typen zu verwenden, kann der Entwickler den Konstruktorparameter auf den bestimmten Typ beschränken.Zusammenfassend lohnt es sich oft, Typen von generischen Typen abzuleiten - dies ermöglicht gegebenenfalls expliziteren Code und verbessert die Lesbarkeit und Wartbarkeit.
quelle
Grundsätzlich gibt es keinen Unterschied zwischen dem Erben von generischen Typen und dem Erben von etwas anderem.
Aber warum um alles in der Welt würden Sie aus einer Klasse stammen und nichts weiter hinzufügen?
quelle
Ich kenne C # nicht sehr gut, aber ich glaube, dies ist die einzige Möglichkeit, ein zu implementieren
typedef
, das C ++ - Programmierer aus verschiedenen Gründen verwenden:Sie haben im Grunde genommen den letzten Vorteil durch die Wahl langweiliger Gattungsnamen zunichte gemacht, aber die beiden anderen Gründe bleiben bestehen.
quelle
StringList
ist einList<string>
- sie können austauschbar verwendet werden. Dies ist wichtig, wenn Sie mit anderen APIs arbeiten (oder wenn Sie Ihre API verfügbar machen), da alle anderen Benutzer auf der Welt diese verwendenList<string>
.using StringList = List<string>;
ist das nächste C # -Äquivalent einer Typendefinition. Eine solche Unterklasse verhindert die Verwendung von Konstruktoren mit Argumenten.using
diese auf die Quellcodedatei beschränkt, in der sich dieusing
befindet. Aus dieser Sicht ist die Vererbung nähertypedef
. Und das Erstellen einer Unterklasse hindert eine Unterklasse nicht daran, alle benötigten Konstruktoren hinzuzufügen (obwohl dies möglicherweise mühsam ist).using
Kann in C # nicht für diesen Zweck verwendet werden, aber die Vererbung kann. Dies zeigt, warum ich Pete Kirkhams positivem Kommentar nicht zustimme - ich halte das nicht fürusing
eine echte Alternative zu C ++typedef
.Mir fällt mindestens ein praktischer Grund ein.
Durch das Deklarieren von nicht generischen Typen, die von generischen Typen erben (auch ohne Hinzufügen von Elementen), können diese Typen an Stellen referenziert werden, an denen sie sonst nicht verfügbar wären oder auf eine komplexere Weise verfügbar wären, als sie sollten.
Ein solcher Fall ist das Hinzufügen von Einstellungen über den Einstellungseditor in Visual Studio, in dem anscheinend nur nicht generische Typen verfügbar sind. Wenn Sie dorthin gehen, werden Sie feststellen, dass alle
System.Collections
Klassen verfügbar sind, jedoch keine Sammlungen von vorhanden sindSystem.Collections.Generic
.Ein weiterer Fall ist das Instanziieren von Typen über XAML in WPF. Bei Verwendung eines Schemas vor 2009 wird die Übergabe von Typargumenten in der XML-Syntax nicht unterstützt. Dies wird durch die Tatsache verschlimmert, dass das 2006-Schema der Standard für neue WPF-Projekte zu sein scheint.
quelle
Ich denke, Sie müssen in die Geschichte schauen .
Ansonsten hatte Theodoros Chatzigiannakis die besten Antworten, zB gibt es immer noch viele Tools, die keine generischen Typen verstehen. Oder vielleicht sogar eine Mischung aus seiner Antwort und seiner Geschichte.
quelle
In einigen Fällen ist es nicht möglich, generische Typen zu verwenden. Beispielsweise können Sie in XAML keinen generischen Typ referenzieren. In diesen Fällen können Sie einen nicht generischen Typ erstellen, der von dem generischen Typ erbt, den Sie ursprünglich verwenden wollten.
Wenn möglich, würde ich es vermeiden.
Im Gegensatz zu einem Typedef erstellen Sie einen neuen Typ. Wenn Sie die StringList als Eingabeparameter für eine Methode verwenden, können Ihre Aufrufer a nicht übergeben
List<string>
.Außerdem müssten Sie alle Konstruktoren, die Sie verwenden möchten, explizit kopieren, was Sie im StringList-Beispiel nicht sagen können
new StringList(new[]{"a", "b", "c"})
, obwohl SieList<T>
einen solchen Konstruktor definieren.Bearbeiten:
Die akzeptierte Antwort konzentriert sich auf die Profis, wenn die abgeleiteten Typen in Ihrem Domain-Modell verwendet werden. Ich bin damit einverstanden, dass sie in diesem Fall möglicherweise nützlich sein können, aber ich persönlich würde sie sogar dort meiden.
Sie sollten sie jedoch (meiner Meinung nach) niemals in einer öffentlichen API verwenden, da Sie entweder das Leben Ihres Anrufers verkürzen oder die Leistung beeinträchtigen (implizite Conversions gefallen mir im Allgemeinen auch nicht wirklich).
quelle
Nachstehend finden Sie ein Beispiel dafür, wie das Erben von einer generischen Klasse im Microsoft Roslyn-Compiler verwendet wird, ohne den Namen der Klasse zu ändern. (Ich war so verblüfft, dass ich hier auf meiner Suche gelandet bin, um zu sehen, ob dies wirklich möglich ist.)
Im Projekt CodeAnalysis finden Sie diese Definition:
Dann gibt es in Projekt CSharpCodeanalysis diese Definition:
Diese nicht generische PEModuleBuilder-Klasse wird häufig im CSharpCodeanalysis-Projekt verwendet, und mehrere Klassen in diesem Projekt erben direkt oder indirekt davon.
Und dann gibt es im Projekt BasicCodeanalysis diese Definition:
Da wir (hoffentlich) davon ausgehen können, dass Roslyn von Leuten mit umfassenden Kenntnissen in C # geschrieben wurde und wie es verwendet werden sollte, denke ich, dass dies eine Empfehlung der Technik ist.
quelle
Es gibt drei mögliche Gründe, warum jemand versucht, das aus meinem Kopf heraus zu tun:
1) Vereinfachung der Erklärungen:
Sicher
StringList
undListString
ungefähr gleich lang, aber stellen Sie sich stattdessen vor, Sie arbeiten mit einemUserCollection
tatsächlichenDictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>
oder einem anderen großen generischen Beispiel.2) Um AOT zu helfen:
Manchmal müssen Sie die Ahead Of Time-Kompilierung und nicht die Just In Time-Kompilierung verwenden, z. B. für iOS. Manchmal hat der AOT-Compiler Schwierigkeiten, alle generischen Typen im Voraus herauszufinden, und dies könnte ein Versuch sein, ihm einen Hinweis zu geben.
3) So fügen Sie Funktionen hinzu oder entfernen Abhängigkeiten:
Vielleicht haben sie spezifische Funktionalität , dass sie für implementieren wollen
StringList
(in einem Erweiterungsmethode versteckt werden könnten, noch nicht oder historische hinzugefügt) , dass sie entweder nicht hinzufügen könnenList<string>
von ihm ohne Erben, oder dass sie zu verschmutzen wollen nichtList<string>
mit .Alternativ möchten sie möglicherweise die Implementierung von
StringList
Down-the-Track ändern , haben also die Klasse gerade erst als Marker verwendet (normalerweise würden Sie hierfür Schnittstellen erstellen / verwenden, zIList
. B. ).Ich würde auch bemerken, dass Sie diesen Code in Irony gefunden haben, einem Sprachparser - eine ziemlich ungewöhnliche Art von Anwendung. Es kann einen anderen bestimmten Grund dafür geben, dass dies verwendet wurde.
Diese Beispiele zeigen, dass es legitime Gründe geben kann, eine solche Erklärung zu verfassen - auch wenn sie nicht allzu häufig ist. Wie bei allem, sollten Sie mehrere Optionen in Betracht ziehen und die für Sie jeweils beste auswählen.
Wenn Sie sich den Quellcode ansehen , scheint Option 3 der Grund zu sein - sie verwenden diese Technik, um Funktionen hinzuzufügen / Sammlungen zu spezialisieren:
quelle
List<T>
ist, aber er muss denStringList
Kontext überprüfen, um zu verstehen, was sie ist. In Wirklichkeit heißt es einfachList<string>
anders. In einem so einfachen Fall scheint dies wirklich keinen semantischen Vorteil zu haben. Sie ersetzen einfach einen bekannten Typ durch den gleichen Typ mit einem anderen Namen.Ich würde sagen, es ist keine gute Praxis.
List<string>
kommuniziert "eine generische Liste von Zeichenfolgen". EsList<string>
gelten eindeutig Erweiterungsmethoden. Zum Verstehen ist weniger Komplexität erforderlich. es wird nur eine Liste benötigt.StringList
kommuniziert "ein Bedürfnis nach einer eigenen Klasse". EventuellList<string>
gelten Erweiterungsmethoden (prüfen Sie hierzu die tatsächliche Typimplementierung). Um den Code zu verstehen, ist mehr Komplexität erforderlich. Liste und die abgeleiteten Klassenimplementierungskenntnisse sind erforderlich.quelle
List<string>
.