MYTH #1: STRUKTUREN SIND LEICHTE KLASSEN
Dieser Mythos wird in verschiedenen Formen geäußert. Einige glauben, dass Werttypen keine Methoden oder andere bedeutende Verhaltensweisen haben können oder sollten - sie sollten als einfache Datentransfertypen verwendet werden, mit nur öffentlichen Feldern oder einfachen Eigenschaften. Der DateTime-Typ ist ein gutes Gegenbeispiel dazu: Es ergibt Sinn, dass er ein Werttyp ist, in Bezug darauf, eine grundlegende Einheit wie eine Zahl oder ein Zeichen zu sein, und es ergibt auch Sinn, dass er Berechnungen basierend auf seinem Wert durchführen kann. Wenn man die Dinge aus der anderen Richtung betrachtet, sollten Datentransfertypen oft Referenztypen sein – die Entscheidung sollte auf den gewünschten Wert- oder Referenztyp-Semantiken basieren, nicht auf der Einfachheit des Typs. Andere glauben, dass Werttypen in Bezug auf die Leistungsfähigkeit "leichter" als Referenztypen sind. Die Wahrheit ist, dass in einigen Fällen Werttypen leistungsfähiger sind – sie erfordern keine Garbage Collection, es sei denn, sie werden geboxt, haben keinen Overhead für die Typidentifizierung und erfordern kein Dereferenzieren, zum Beispiel. Aber in anderer Hinsicht sind Referenztypen leistungsfähiger – Parameterübergabe, Zuweisung von Werten an Variablen, Rückgabe von Werten und ähnliche Operationen erfordern nur 4 oder 8 Bytes zu kopieren (je nachdem, ob Sie die 32-Bit oder 64-Bit CLR ausführen) anstatt alle Daten zu kopieren. Stellen Sie sich vor, ArrayList wäre irgendwie ein "reiner" Werttyp, und das Übergeben eines ArrayList-Ausdrucks an eine Methode würde das Kopieren aller seiner Daten beinhalten! In den meisten Fällen hängt die Leistungsfähigkeit sowieso nicht von dieser Art von Entscheidung ab. Engpässe sind fast nie da, wo Sie denken, und bevor Sie eine Designentscheidung auf Basis der Leistungsfähigkeit treffen, sollten Sie die verschiedenen Optionen messen. Es ist erwähnenswert, dass die Kombination der beiden Überzeugungen auch nicht funktioniert. Es spielt keine Rolle, wie viele Methoden ein Typ hat (ob es sich um eine Klasse oder eine Struktur handelt) – der Speicherbedarf pro Instanz wird nicht beeinflusst. (Es entsteht ein Kostenfaktor in Bezug auf den Speicherbedarf für den Code selbst, aber das gilt nur einmal und nicht für jede Instanz.)
MYTH #2: REFERENZTYPEN LEBEN AUF DEM HEAP; WERTTYPEN LEBEN AUF DEM STACK
Dies wird oft durch Nachlässigkeit der Person verursacht, die es wiederholt. Der erste Teil ist korrekt - eine Instanz eines Referenztyps wird immer im Heap erstellt. Es ist der zweite Teil, der Probleme verursacht. Wie ich bereits erwähnt habe, lebt der Wert einer Variablen da, wo er deklariert ist. Wenn Sie also eine Klasse mit einer Instanzvariablen vom Typ int haben, wird der Wert dieser Variablen für jedes gegebene Objekt immer dort sein, wo auch der Rest der Daten für das Objekt ist – im Heap. Nur lokale Variablen (Variablen, die innerhalb von Methoden deklariert sind) und Methodenparameter leben auf dem Stack. In C# 2 und später leben selbst einige lokale Variablen nicht wirklich auf dem Stack, wie Sie in Kapitel 5 sehen werden. SIND DIESE KONZEPTE JETZT RELEVANT? Es lässt sich argumentieren, dass Sie, wenn Sie verwalteten Code schreiben, das Speichermanagement dem Laufzeitsystem überlassen sollten. Tatsächlich gibt die Sprachspezifikation keine Garantien darüber, was wo lebt; eine zukünftige Runtime könnte in der Lage sein, einige Objekte auf dem Stack zu erzeugen, wenn sie damit davonkommen kann, oder der C#-Compiler könnte Code generieren, der überhaupt kaum den Stack nutzt. Der nächste Mythos ist normalerweise nur ein terminologisches Problem.
MYTH #3: OBJECTS WERDEN STANDARDMÄSSIG BEI C# PER REFERENZ ÜBERGEBEN
Dies ist wahrscheinlich der am weitesten verbreitete Mythos. Die Menschen, die diese Behauptung aufstellen, wissen oft (aber nicht immer), wie C# tatsächlich funktioniert, wissen aber nicht, was "per Referenz übergeben" wirklich bedeutet. Leider ist dies für Menschen, die wissen, was es bedeutet, verwirrend. Die formale Definition von "per Referenz übergeben" ist relativ kompliziert, und beinhaltet Begriffe wie l-Werte und ähnliches aus der Informatik, aber das Wichtige ist, dass wenn Sie eine Variable per Referenz übergeben, die Methode, die Sie aufrufen, den Wert der Variablen des Aufrufers ändern kann, indem sie den Wert ihres Parameters ändert. Denken Sie daran, dass der Wert einer Referenztypen-Variablen die Referenz, nicht das Objekt selbst ist. Sie können den Inhalt des Objekts, auf das sich ein Parameter bezieht, ändern, ohne dass der Parameter selbst per Referenz übergeben wird. Beispielsweise ändert die folgende Methode den Inhalt des betreffenden StringBuilder-Objekts, aber der Ausdruck des Aufrufers wird immer noch auf dasselbe Objekt verweisen wie zuvor:
void AppendHello(StringBuilder builder)
{
builder.Append("hello");
}
Wenn diese Methode aufgerufen wird, wird der Parameterwert (eine Referenz auf einen StringBuilder) per Wert übergeben. Wenn Sie den Wert der builder-Variablen innerhalb der Methode ändern würden - zum Beispiel mit der Anweisung builder = null; – würde diese Änderung nicht vom Aufrufer gesehen werden, im Gegensatz zum Mythos. Interessanterweise ist nicht nur das "per Referenz" Teil des Mythos ungenau, sondern auch das "Objekte werden übergeben". Objekte selbst werden weder per Referenz noch per Wert übergeben. Wenn ein Referenztyp involviert ist, wird entweder die Variable per Referenz übergeben oder der Wert des Arguments (die Referenz) wird per Wert übergeben. Unter anderem beantwortet dies die Frage, was passiert, wenn null als by-value-Argument verwendet wird - wenn Objekte übergeben würden, würde das Probleme verursachen, da es kein Objekt gäbe, das übergeben werden könnte! Stattdessen wird die Nullreferenz genauso wie jede andere Referenz per Wert übergeben. Wenn Sie nach dieser kurzen Erklärung verwirrt sind, sollten Sie vielleicht meinen Artikel "Parameterübergabe in C#" (http://mng.bz/otVt) ansehen, der viel detaillierter darauf eingeht. Diese Mythen sind nicht die einzigen, die herumgehen. Auch das Boxing und Unboxing stoßen auf ihr gerechtes Maß an Missverständnissen, die ich als nächstes versuchen werde aufzuklären.
Referenz: C# in Depth 3rd Edition von Jon Skeet
0 Stimmen
Ich kann nur dem ersten Punkt zustimmen, Strukturen werden zum Beispiel sehr häufig in der Spieleprogrammierung verwendet.
305 Stimmen
System.Drawing.Rectangle
verletzt alle drei dieser Regeln.3 Stimmen
Ja, nun gut, zumindest Teile davon. Ich weiß, dass es für Teile von Spielen verwendet wird, wie NWN2 World Creator. C ist in der Regel immer noch das Kernelement (Engine). XNA Game Studio, Google es :)
7 Stimmen
Es gibt ziemlich viele kommerzielle Spiele, die in C# geschrieben sind, der Punkt ist, dass sie für optimierten Code verwendet werden.
2 Stimmen
Ich verwende sie manchmal für Zeichenfolgenwerte "enums".
4 Stimmen
Eine weitere Situation, in der Sie Strukturen verwenden sollten, ist, wenn Ihre Objekte keine Identität benötigen.
1 Stimmen
Ich bin immer nach dem Denken in der zweiten Bearbeitung der Frage gegangen: Wenn ich eine Handvoll verwandter Werttypen habe, erstelle ich eine Struktur. Ich könnte mehrere dieser kleinen Dateneinheiten haben und möchte wirklich nicht viele zusätzliche Klassendateien erstellen für das, was nur ein paar Dutzend Codezeilen wären.
1 Stimmen
Sie verwenden Strukturen, wenn Sie Ihre Klasse nicht vererben möchten. Das ist großartig, wenn Sie Objekte erstellen möchten, die nur Daten halten.
36 Stimmen
Strukturen bieten eine bessere Leistung, wenn Sie kleine Sammlungen von Werttypen haben, die Sie zusammen gruppieren möchten. Das passiert die ganze Zeit beim Spielprogrammieren, zum Beispiel wird ein Eckpunkt in einem 3D-Modell eine Position, eine Texturkoordinate und eine Normale haben, außerdem wird er in der Regel unveränderlich sein. Ein einzelnes Modell kann ein paar tausend Eckpunkte haben oder auch nur ein Dutzend, aber Strukturen bieten insgesamt weniger Overhead in diesem Nutzungsszenario. Das habe ich durch mein eigenes Engine-Design verifiziert.
0 Stimmen
Ich würde Strukturen für die Verbindung von nicht verwaltetem Code und leichtgewichtige statische Datensätze verwenden. Der Kompromiss zwischen Leistungssteigerung durch Speicherverwaltung und mangelnder Erweiterbarkeit im Design.
3 Stimmen
Das ist der völlig falsche Grund, um sich für Strukturen anstelle von Klassen zu entscheiden
6 Stimmen
@ErikForbes: Ich denke, dies wird allgemein als der größte BCL "oops" angesehen
0 Stimmen
@ChrisW: Der größte Defekt in
Rechteck
ist, dass seine Elemente Eigenschaften anstatt Felder sind. Die Verwendung von struct Eigenschaften, wenn Felder ausgereicht hätten, ist wahrscheinlich zu 95% für die Vorstellung verantwortlich, dass "veränderbare structs böse sind" (es gibt legitime Verwendungen für Eigenschaften-Setter in schreibgeschützten Strukturinstanzen; deshalb hat C# erst kürzlich ihre Verwendung verboten (legitime Anwendungsfälle ausschließend); Strukturen, die Elemente in Lese-Schreib-Eigenschaften einhüllten, verwandelten effektiv Code, der Compilerfehler hätte erzeugen sollen, in Code, der kompilierte, aber nicht funktionierte.0 Stimmen
Möglicher Duplikat von Wann sollte ich eine Struktur anstelle einer Klasse verwenden?
0 Stimmen
Ein interessanter Punkt könnte sein, wann sollte ich Strukturen mit Methoden verwenden, weil jeder Methode 1 Wort (16 Bytes) mehr in Bezug auf die Struktursumme .. also wird diese Struktur offensichtlich schwer genug sein, um Regel #2 zu brechen und die Leistungsfähigkeit zu beeinträchtigen
2 Stimmen
@ChrisW Stellt
System.Drawing.Rectangle
nicht einen einzigen Wert dar? Könnten Sie das bitte erklären?0 Stimmen
@MarsonMao Es stellt eine linke, eine rechte, eine obere, eine untere, eine Höhe und eine Breite dar (d. h. 6 Werte, zugegebenermaßen miteinander verwandte Werte).
8 Stimmen
@ChrisW Ich sehe, aber repräsentieren diese Werte nicht ein Rechteck, das heißt, einen "einzigen" Wert? Wie Vector3D oder Color, sie enthalten auch mehrere Werte, aber ich denke, sie repräsentieren einzelne Werte?
2 Stimmen
@MarsonMao Eines der bestimmenden Merkmale einer Struktur ist, dass es sich um eine Gruppe von verwandten Werten handelt, die als einzelner Wert behandelt werden sollen. Ein Rechteck erfüllt diese Eigenschaft sicherlich. Ich glaube, dass Chris versucht, Haare in der Wortwahl dieses Punktes zu spalten.
2 Stimmen
Beide Links sind defekt.
1 Stimmen
@weberc2 Chris sagt, diese Liste sollte nicht verwendet werden, um zu definieren, ob eine Struktur verwendet werden soll. Gegenbeispiel ist
Rechteck
: etwas, das auf jeden Fall eine Struktur sein sollte, aber trotzdem all dies verletzt. Daher sind sie keine guten Bedingungen.0 Stimmen
Das war eine gute Frage. Ich habe viel über die Unterschiede gelesen und hier habe ich schließlich die Antwort gefunden.