Beim Programmieren in C # bin ich auf eine seltsame Entscheidung für das Sprachdesign gestoßen, die ich einfach nicht verstehe.
C # (und die CLR) haben also zwei aggregierte Datentypen: struct
(Werttyp, auf dem Stapel gespeichert, keine Vererbung) und class
(Referenztyp, auf dem Heap gespeichert, hat Vererbung).
Dieses Setup hört sich zunächst gut an, aber dann stoßen Sie auf eine Methode, die einen Parameter eines Aggregattyps verwendet. Um herauszufinden, ob es sich tatsächlich um einen Werttyp oder einen Referenztyp handelt, müssen Sie die Typdeklaration ermitteln. Es kann manchmal sehr verwirrend werden.
Die allgemein akzeptierte Lösung des Problems scheint darin zu bestehen, dass alle struct
s als "unveränderlich" deklariert werden (indem ihre Felder auf "unveränderlich" gesetzt werden readonly
), um mögliche Fehler zu vermeiden und die struct
Nützlichkeit von s einzuschränken .
C ++ verwendet beispielsweise ein viel besser verwendbares Modell: Es ermöglicht Ihnen, eine Objektinstanz entweder auf dem Stapel oder auf dem Heap zu erstellen und sie als Wert oder als Referenz (oder als Zeiger) zu übergeben. Ich höre immer wieder, dass C # von C ++ inspiriert wurde, und ich kann einfach nicht verstehen, warum es diese eine Technik nicht übernommen hat. Das Kombinieren von class
und struct
zu einem Konstrukt mit zwei verschiedenen Zuordnungsoptionen (Heap und Stack) und deren Weitergabe als Werte oder (explizit) als Referenzen über die Schlüsselwörter ref
und out
scheint eine nette Sache zu sein.
Die Frage ist, warum haben class
und struct
werden getrennte Konzepte in C # und der CLR anstelle von einem Aggregat Typ mit zwei Zuweisungsoptionen?
quelle
struct
s nicht immer auf dem Stapel gespeichert. Betrachten Sie ein Objekt mit einemstruct
Feld. Davon abgesehen, wie Mason Wheeler erwähnte, ist das Problem des Aufschneidens wahrscheinlich der größte Grund.Antworten:
Der Grund, warum C # (und Java und im Wesentlichen jede andere OO-Sprache, die nach C ++ entwickelt wurde) das C ++ - Modell in diesem Aspekt nicht kopierten, ist, dass die Art und Weise, wie C ++ dies tut, ein schreckliches Durcheinander ist.
Sie haben oben die relevanten Punkte korrekt identifiziert
struct
:: Werttyp, keine Vererbung.class
: Referenztyp, hat Vererbung. Vererbungs- und Werttypen (oder genauer gesagt Polymorphismus und Pass-by-Wert) passen nicht zusammen. Wenn Sie ein Objekt vom TypDerived
an ein Methodenargument vom Typ übergebenBase
und darauf eine virtuelle Methode aufrufen, können Sie nur sicherstellen, dass es sich bei dem übergebenen Objekt um eine Referenz handelt.Dazwischen und all den anderen Problemen, denen Sie in C ++ begegnen, wenn Sie vererbbare Objekte als Werttypen haben (Kopierkonstruktoren und Objektschnitte fallen Ihnen ein !), Ist die beste Lösung, einfach Nein zu sagen.
Gutes Sprachdesign implementiert nicht nur Funktionen, sondern weiß auch, welche Funktionen nicht implementiert werden sollen. Eine der besten Möglichkeiten, dies zu tun, besteht darin, aus den Fehlern derjenigen zu lernen, die vor Ihnen aufgetreten sind.
quelle
Analog ist C # im Grunde genommen wie ein Satz von Mechanikerwerkzeugen, bei denen jemand gelesen hat, dass Sie Zangen und verstellbare Schraubenschlüssel generell vermeiden sollten, so dass verstellbare Schraubenschlüssel überhaupt nicht enthalten sind und die Zangen in einer speziellen Schublade mit der Kennzeichnung "unsicher" verriegelt sind. , und können nur mit Zustimmung eines Vorgesetzten verwendet werden, nachdem ein Haftungsausschluss unterzeichnet wurde, der Ihren Arbeitgeber von jeglicher Verantwortung für Ihre Gesundheit entbindet.
Im Vergleich dazu enthält C ++ nicht nur verstellbare Schraubenschlüssel und Zangen, sondern auch einige Sonderwerkzeuge, deren Zweck nicht sofort ersichtlich ist. Wenn Sie nicht wissen, wie Sie sie richtig halten können, können sie Ihnen leicht die Hand nehmen Daumen (aber wenn Sie erst einmal wissen, wie man sie benutzt, können Sie mit den grundlegenden Werkzeugen in der C # -Toolbox Dinge tun, die im Grunde unmöglich sind). Darüber hinaus gibt es eine Drehmaschine, Fräsmaschine, Flachschleifmaschine, Metallbandsäge usw., mit denen Sie jederzeit ganz neue Werkzeuge entwerfen und herstellen können, wenn Sie dies für erforderlich halten (aber ja, die Werkzeuge dieser Maschinisten können und werden dies bewirken Schwere Verletzungen, wenn Sie nicht wissen, was Sie mit ihnen machen - oder wenn Sie einfach nur nachlässig werden.
Dies spiegelt den grundlegenden Unterschied in der Philosophie wider: C ++ versucht, Ihnen alle Werkzeuge zur Verfügung zu stellen, die Sie für praktisch jedes gewünschte Design benötigen. Es wird fast kein Versuch unternommen, zu steuern, wie Sie diese Werkzeuge verwenden. Daher ist es auch einfach, mit ihnen Entwürfe zu erstellen, die nur in seltenen Situationen gut funktionieren, und Entwürfe, die wahrscheinlich nur eine schlechte Idee sind und von denen niemand weiß, in welcher Situation Sie werden wahrscheinlich überhaupt gut funktionieren. Insbesondere wird ein Großteil davon durch die Entkopplung von Entwurfsentscheidungen erzielt - auch wenn diese in der Praxis fast immer gekoppelt sind. Infolgedessen gibt es einen großen Unterschied zwischen dem einfachen Schreiben von C ++ und dem guten Schreiben von C ++. Um C ++ gut zu schreiben, müssen Sie eine Menge Redewendungen und Faustregeln kennen (einschließlich Faustregeln, wie ernst es ist, sie zu überdenken, bevor Sie andere Faustregeln brechen). Als Ergebnis, C ++ orientiert sich viel mehr an der Benutzerfreundlichkeit (von Experten) als an der Leichtigkeit des Lernens. Es gibt auch (allzu viele) Umstände, unter denen es auch nicht wirklich schrecklich einfach ist, es zu benutzen.
C # unternimmt noch viel mehr, um zu erzwingen (oder zumindest äußerst deutlich vorzuschlagen), was die Sprachdesigner als gute Entwurfspraktiken erachteten. Nicht wenige Dinge, die in C ++ entkoppelt sind (aber in der Praxis normalerweise zusammenpassen), sind in C # direkt gekoppelt. Es erlaubt "unsicherem" Code, die Grenzen ein wenig zu verschieben, aber ehrlich gesagt nicht viel.
Das Ergebnis ist, dass es auf der einen Seite einige Designs gibt, die ziemlich direkt in C ++ ausgedrückt werden können und in C # wesentlich ungeschickter auszudrücken sind. Auf der anderen Seite ist es eine ganze Menge einfacher C #, und die Chancen zur Herstellung ein wirklich schreckliches Designs , die Arbeit für Ihre Situation nicht lernen (oder wahrscheinlich andere) werden drastisch reduziert. In vielen (wahrscheinlich sogar den meisten) Fällen können Sie ein solides, funktionsfähiges Design erhalten, indem Sie sozusagen "im Fluss" bleiben. Oder, als einer meiner Freunde (zumindest ich sehe ihn gerne als Freund - nicht sicher, ob er wirklich einverstanden ist), macht es C # leicht, in die Grube des Erfolgs zu fallen.
Wenn Sie sich also genauer mit der Frage befassen, wie
class
undstruct
wie sie sich in den beiden Sprachen befinden: Objekte, die in einer Vererbungshierarchie erstellt wurden, in der Sie möglicherweise ein Objekt einer abgeleiteten Klasse als Basisklasse / Schnittstelle verwenden, sind Sie ziemlich fertig stecken geblieben mit der Tatsache, dass Sie dies normalerweise über einen Zeiger oder eine Referenz tun müssen - auf einer konkreten Ebene passiert, dass das Objekt der abgeleiteten Klasse etwas Gedächtnis enthält, das als eine Instanz der Basisklasse behandelt werden kann / Schnittstelle, und das abgeleitete Objekt wird über die Adresse dieses Teils des Speichers manipuliert.In C ++ ist es Sache des Programmierers, dies korrekt zu tun. Wenn er die Vererbung verwendet, muss er sicherstellen, dass (zum Beispiel) eine Funktion, die mit polymorphen Klassen in einer Hierarchie arbeitet, dies über einen Zeiger oder einen Verweis auf die Basis tut Klasse.
In C # ist die grundsätzlich gleiche Trennung zwischen den Typen viel eindeutiger und wird von der Sprache selbst erzwungen. Der Programmierer muss keine Schritte ausführen, um eine Instanz einer Klasse als Referenz zu übergeben, da dies standardmäßig geschieht.
quelle
Dies ist aus "C #: Warum brauchen wir eine andere Sprache?" - Gunnerson, Eric:
Die Referenzsemantik für Objekte ist eine Möglichkeit, viele Probleme zu vermeiden (natürlich und nicht nur das Aufteilen von Objekten), aber bei Problemen in der realen Welt können manchmal Objekte mit einer Wertsemantik erforderlich sein (z. B. ein Blick auf Klänge, die ich niemals als Referenzsemantik verwenden sollte, oder? aus einem anderen Blickwinkel).
Gibt es einen besseren Ansatz, als diese schmutzigen, hässlichen und schlechten Objekte mit Wertsemantik unter dem Markennamen zu trennen
struct
?quelle
int[]
gemeinsam genutzt oder geändert werden kann (Arrays können eine von beiden sein, aber im Allgemeinen nicht beide), dazu beitragen, dass falscher Code falsch aussieht.Anstatt an
Object
Werttypen zu denken , die von diesen abgeleitet sind, wäre es sinnvoller, sich Speicherorttypen vorzustellen, die in einem völlig anderen Universum als Klasseninstanztypen existieren, aber für jeden Werttyp muss ein entsprechender Heap-Objekttyp vorhanden sein. Ein Speicherort des Strukturtyps enthält einfach eine Verkettung der öffentlichen und privaten Felder des Typs, und der Heap-Typ wird nach einem Muster wie dem folgenden automatisch generiert:und für eine Anweisung wie: Console.WriteLine ("Der Wert ist {0}", somePoint);
zu übersetzen als: boxed_Point box1 = new boxed_Point (somePoint); Console.WriteLine ("Der Wert ist {0}", box1);
In der Praxis ist es nicht erforderlich, die Heap-Instanztypen wie z. B. aufzurufen, da Speicherorttypen und Heap-Instanztypen in separaten Universen vorhanden sind
boxed_Int32
. da das System wissen würde, welche Kontexte die Heap-Objekt-Instanz benötigen und welche einen Speicherort benötigen.Einige Leute denken, dass alle Werttypen, die sich nicht wie Objekte verhalten, als "böse" angesehen werden sollten. Ich bin der gegenteiligen Ansicht: Da Speicherorte von Werttypen weder Objekte noch Verweise auf Objekte sind, sollte die Erwartung, dass sie sich wie Objekte verhalten sollten, als nicht hilfreich angesehen werden. In Fällen, in denen sich eine Struktur sinnvoll wie ein Objekt verhalten kann, ist es nichts Falsches, wenn sie dies tut, aber jedes
struct
ist in seinem Herzen nichts anderes als eine Ansammlung von öffentlichen und privaten Feldern, die mit Klebeband zusammengeklebt sind.quelle