Ich weiß, dass Generika verwendet werden, um Typensicherheit zu erreichen, und ich lese häufig, dass sie weitgehend in benutzerdefinierten Sammlungen verwendet werden. Aber warum müssen wir sie eigentlich generisch haben?
Zum Beispiel
Warum kann ich string[]
anstelle von List<string>
nicht verwenden?
Nehmen wir an, ich deklariere eine generische Klasse und sie hat einen generischen Typparameter X.
%Vor%Wenn ich eine Methode für die Klasse zur Verfügung stelle, die
tut %Vor% Was heißt das eigentlich? Ich weiß nicht, was T
wirklich sein wird und ich weiß nicht, was x = x + 1
wirklich machen wird.
Wenn ich nicht in der Lage bin, meine eigenen Manipulationen an meinen Methoden durchzuführen, wie werden die Generika mir trotzdem helfen?
Ich habe schon viele Bücherbuch-Antworten studiert. Es würde sehr geschätzt werden, wenn irgendjemand hier einige klare Einsichten liefern könnte.
Grüße, NLV
Ihre Frage beantwortet sich selbst. Array-Typen sind selbst eine Art generische Typisierung. Hätten wir ein generisches Typsystem in CLR v1.0, würde ich wetten, dass es keinen speziellen Array-Typ geben würde, würde es nur geben Array<T>
zusammen mit List<T>
und so weiter. Da die erste Version der CLR keine generischen Typen hatte, setzen wir den wichtigsten und offensichtlichsten generischen Typ, den Array-Typ, ein. Und deshalb gibt es eine ganze Menge Spezial-Code, um Arrays zu behandeln, den einen generischen Typ, den die CLR in Version 1.0 unterstützt.
Da Arrays im Wesentlichen ein generischer Typ sind, beantwortet sich Ihre Frage selbst: Der Grund für generische Typen im Allgemeinen ist derselbe Grund, der die Erstellung des Array-Typ-Musters motiviert. Das heißt: Ein Array-Typ verstärkt die Fähigkeit des zugrunde liegenden Typs auf eine bestimmte Art und Weise .
Ein int steht für eine Zahl. Ein int [] repräsentiert eine Sammlung von Zahlen; Wir haben den Begriff der Zahl zum Begriff einer Zahlensammlung verstärkt. Ein Kunde repräsentiert einen Kunden. Ein Kunde [] repräsentiert den Begriff einer Kundensammlung. Wir haben den Begriff des Kunden auf den Begriff der Sammlung von Kunden erweitert. Die Zuordnung vom Typ T zum Typ T [] steht für den abstrakten Begriff der generischen Erweiterung einer Instanz eines Typs auf eine Sammlung von Instanzen des Typs.
Dieselbe Begründung motiviert alle generischen Typen. Der Nullable<T>
-Typ verstärkt einen Typ auf den Begriff "dieses Ding könnte eine Instanz des Typs sein". Der IComparable<T>
-Typ verstärkt einen Typ auf den Begriff "eine Instanz dieses Typs kann in Bezug auf eine andere Instanz angeordnet werden". Und so weiter. Jeder generische Typ ist genau wie das Array-Muster: Er repräsentiert die Verstärkung eines Typs in einen neuen Typ, der neue Operationen für diesen Typ bereitstellt.
Kurz gesagt: Der Zweck des generischen Typsystems besteht darin, dass Sie Ihre eigenen Typamplifikationen erfinden und diese Amplifikationen mit dem Typsystem manipulieren können.
Wir verwenden Generika, wenn wir einen Polymorphismus "höherer Ordnung" benötigen, ähnlich einem Duck-Typing . Wenn wir Foo<T>
sagen, meinen wir, dass Foo<>
von einer Eigenschaft abhängt, die jede mögliche T
haben kann, und entweder die angegebene T
diese Eigenschaft hat oder der Code nicht kompiliert werden kann.
Wenn wir x = x + 1
angeben, implizieren wir, dass T
1
hinzufügen kann und dass der Rückgabetyp T
ist.
Aber warum müssen wir es eigentlich generisch haben?
Wenn Sie möchten, können Sie auch viele benutzerdefinierte Klassen erstellen: StringList
, IntList
, DoubleList
usw. Aber der generische Punkt ist, dass Sie Ihre Klasse einmal definieren können und sie für alle Objekte funktioniert .
Und x = x + 1
wobei x
vom generischen Typ ist T
benötigt den tatsächlichen realen -Typ, um entweder die Addition mit int zu unterstützen oder den operator +
mit int as zu überladen der zweite Typ.
Eigentlich arbeite ich an einer Website, wo Generika meine Arbeit viel einfacher machen. Ich muss auf viele Informationen aus einer Datenbank zugreifen, da die gesamte Site datenbankgesteuert ist. Daher wollte ich wirklich eine einzige Klasse haben, um die Datenbankfunktionalität zu implementieren. Diese Klasse wird von mehr Klassen geerbt, die wissen, wie sie mit ihren eigenen Daten umgehen.
Ich habe zum Beispiel eine Products-Klasse, die sich mit Produktdaten beschäftigt, und eine Themenklasse, die sich mit Themen beschäftigt, etc. Nun, all diese Klassen benötigten ein gemeinsames Format zum Lesen und Schreiben. Also habe ich eine Record-Klasse erstellt, die das behandelt.
Hier beginnt der Generics-Spaß. Ich habe eine Produktklasse, eine Themenklasse usw. erstellt. mit stark typisierten Mitgliedern wie Name, Hersteller, ThemeId, aber die Records-Klasse hat keine Ahnung, wie man mit ihnen umgeht. Daher wird die Datenbankklasse mit einem Typ instanziiert, der sich auf die bestimmte Klasse, Produkt, Thema usw. bezieht. und die Record-Klasse verwendet auch einen generischen Typ, also kann ich Code schreiben wie ...
%Vor%Speichern Sie es dann wie ...
%Vor%Es spart nicht nur viel Tipparbeit, sondern ermöglicht mir auch eine einzige Klasse, die all die schmutzige Arbeit erledigt, während diese kleinen "Stub" -Klassen mir eine gut lesbare stark typisierte Schnittstelle zur Verfügung stellen.
Wie auch immer, Entschuldigung für die langatmige Post. Hoffe, dass dies hilft Ihnen, die Macht von Generika in mindestens dieser Instanz zu verstehen.
Für Ihre erste Frage könnten Sie List<string>
über string[]
wählen, wenn Sie der Liste dynamisch Strings hinzufügen wollten - ein Array hat eine feste Größe.
Da while string [] unterstützt wird (d. h. ein benutzerdefinierter Array-Typ wird abgezogen), verfügt ein Array über Folgendes: * Weniger funktional als eine Liste oder sogar eine IList * ist ein Array, das funktioniert nicht für andere Arten von Sammlungen. Was ist mit Wörterbüchern?
Wenn ich nicht in der Lage bin, mein eigenes zu machen Manipulationen in meinen Methoden wie die Generika werden mir helfen trotzdem?
Das ist ein bisschen wie die Frage "Wenn ich ein vegetarisches Restaurant betreibe, was nützt es dann, dass ich Steaks bei meinem Lieferanten kaufen kann?".
Grundsätzlich, wenn Ihre Projekte komplizierter werden, schreiben Sie Ihre eigenen Manipulationen in die Methoden. Sie werden alle Sachen, wo es Sinn macht.
string[]
gibt uns ein Array fester Größe. Um eine Liste dynamischer Größe zu erhalten, benötigen wir eine Klasse. Wenn wir also eine Liste von Strings haben wollen, müssen wir vielleicht eine Klasse namens StringList erstellen. Für Doppel, DoubleList. Und so weiter. Aber was ist mit diesen Klassen für diese Typen? Nichts. Stattdessen erstellen wir eine generische Klasse, List & lt; & gt ;, die jeden Typ akzeptieren kann. Anstatt die gleiche Klasse für verschiedene Typen immer wieder neu erstellen zu müssen, ersparen uns Generika etwas Arbeit.
Wie für das x = x + 1
Problem müssen Sie erwarten, dass T einen +
Operator hat, der auf der rechten Seite einen int akzeptiert. Wenn dies nicht der Fall ist, wird ein Laufzeitfehler ausgelöst. Wenn dies der Fall ist, weist dieser Code x der Ausgabe des +
-Operators mit x
und 1
als Argumente zu.
Es gibt viele andere Fälle, in denen keine eindeutigen Operationen ausgeführt werden und die Klasse / Methode mit mehr als einem Typ arbeiten muss. Dies sind auch Fälle, in denen Generika wichtig und nützlich sind.
Es liegt wirklich an Ihnen zu entscheiden, wann und wo Sie sie in Ihrem Code verwenden. Wenn Sie ein Problem haben, wo sie die beste Antwort sind, großartig. Benutze sie. Ansonsten nicht. Disqualifizieren Sie sie nicht als Lösung, denn sie waren nie die Antwort auf Ihr spezifisches Problem.
Ein gutes Beispiel aus meiner eigenen Arbeitserfahrung war die Verwaltung von Linq-To-SQL-Typen. Eine grundlegende Operation, die ich beim Erstellen eines neuen Datenbankobjekts ausführen muss, ist:
In diesem speziellen Projekt gibt es ungefähr 100 verschiedene Objekttypen, also wäre das Schreiben dieses Codes für jeden einzelnen umständlich, sogar mit einem Skript, und was tun wir dann, wenn wir etwas an der Methode ändern müssen? / p>
Unter Verwendung von Generika zusammen mit Einschränkungen kann ich Folgendes tun:
%Vor%Zusätzlich zum Vorteil der Typüberprüfung beim Kompilieren habe ich jetzt eine einzige Funktion, die anstelle einer neuen Version für jede Tabelle in meiner Datenbank implementiert und verwaltet wird.
Beachten Sie, dass, wenn Sie aus dem C ++ - Hintergrund kommen, C # -Generika und C ++ - Vorlagen ähnliche Konzepte sind. Obwohl ich glaube, dass sie tatsächlich anders arbeiten.
Generics existieren, um Code zu schreiben, der für verschiedene Typen wiederverwendbar ist. Generics, erhöht die Sicherheit, reduziert Casting und Boxen.
Angenommen, Sie benötigen einen Stapel Ganzzahlen; Eine Lösung wäre, eine Integer-Stack-Klasse zu entwickeln. Dann würden Sie für jeden erforderlichen Datentyp (float, string usw.) separate Versionen der Klasse schreiben; Hier würden Sie erkennen, dass dies zur Kodierung von Duplikaten neigen würde.
Eine andere Lösung wäre, einen Stapel zu schreiben, der verallgemeinert wird, indem man das Objekt als Elementtyp verwendet:
Wie wäre es mit dem nächsten?
%Vor%Dies ist die generische Klasse
%Vor% Ja, ich weiß, jetzt würden Sie too
sich in Generics verlieben!
Übrigens, wenn Sie jemals planen, Vererbung und Generika zu vergleichen, dann denken Sie daran, dass Vererbung die Wiederverwendbarkeit mit einem Basistyp hat, während Generika die Wiederverwendbarkeit mit einem Schablonentyp ausdrückt.