1628 Stimmen

Wann sollte ich in C# eine Struktur anstelle einer Klasse verwenden?

Wann sollten Sie in C# struct und nicht class verwenden? Mein konzeptionelles Modell besagt, dass structs verwendet werden, wenn das Element nur eine Sammlung von Wertetypen ist. Eine Möglichkeit, sie alle logisch zu einem zusammenhängenden Ganzen zusammenzufassen.

Ich bin auf diese Regeln hier gestoßen:

  • Ein struct sollte einen einzigen Wert repräsentieren.
  • Ein struct sollte einen Speicherbedarf von weniger als 16 Bytes haben.
  • Ein struct sollte nach der Erstellung nicht verändert werden.

Funktionieren diese Regeln? Was bedeutet ein struct semantisch?

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 :)

18voto

Maurice Flanagan Punkte 5081

Sie müssen eine "Struktur" verwenden, wenn Sie den Speicherlayout explizit angeben möchten, indem Sie das StructLayoutAttribute verwenden - in der Regel für PInvoke.

Bearbeitung: Ein Kommentar weist darauf hin, dass Sie Klasse oder Struktur mit StructLayoutAttribute verwenden können, und das ist sicherlich wahr. In der Praxis würden Sie in der Regel eine Struktur verwenden - sie wird auf dem Stapel anstatt auf dem Heap alloziert, was Sinn macht, wenn Sie einfach ein Argument an einen nicht verwalteten Methodenaufruf übergeben.

5 Stimmen

Die StructLayoutAttribute kann auf Strukturen oder Klassen angewendet werden, daher ist dies kein Grund, Strukturen zu verwenden.

0 Stimmen

Warum ergibt es Sinn, wenn Sie nur ein Argument an einen nicht verwalteten Methodenaufruf übergeben?

16voto

mjfgates Punkte 3291

Ich verwende Strukturen zum Verpacken oder Entpacken beliebiger binärer Kommunikationsformate. Dazu gehört das Lesen oder Schreiben auf die Festplatte, DirectX Vertex-Listen, Netzwerkprotokolle oder der Umgang mit verschlüsselten/komprimierten Daten.

Die drei von Ihnen aufgeführten Richtlinien waren für mich in diesem Zusammenhang nicht hilfreich. Wenn ich vierhundert Bytes an Daten in einer bestimmten Reihenfolge ausgeben muss, werde ich eine vierhundert-Byte-Struktur definieren, sie mit den entsprechenden, zufälligen Werten befüllen und sie so einrichten, wie es zum jeweiligen Zeitpunkt am sinnvollsten ist. (Na gut, vierhundert Bytes wären ziemlich seltsam -- aber als ich damals Excel-Dateien geschrieben habe, hatte ich überall Strukturen von bis zu etwa vierzig Bytes, weil das die Größe einiger der BIFF-Datensätze war.)

0 Stimmen

Konntest du nicht einfach einen Referenztyp dafür verwenden?

15voto

Sujit Punkte 3507

.NET unterstützt Werttypen und Referenztypen (in Java können nur Referenztypen definiert werden). Instanzen von Referenztypen werden im verwalteten Heap zugeordnet und werden vom Garbage Collector eingesammelt, wenn keine ausstehenden Verweise mehr vorhanden sind. Instanzen von Werttypen hingegen werden im Stack zugeordnet, und daher wird der zugewiesene Speicher sofort zurückgewonnen, wenn ihr Gültigkeitsbereich endet. Und natürlich werden Werttypen per Wert übergeben und Referenztypen per Referenz. Alle C#-Primitivdatentypen, außer System.String, sind Werttypen.

Wann struct anstatt class verwenden,

In C# sind structs Werttypen, Klassen sind Referenztypen. Sie können in C# Werttypen erstellen, indem Sie das enum-Schlüsselwort und das struct-Schlüsselwort verwenden. Die Verwendung eines Werttyps anstelle eines Referenztyps führt zu weniger Objekten im verwalteten Heap, was zu einer geringeren Belastung des Garbage Collectors (GC), weniger häufigen GC-Zyklen und folglich besseren Leistungen führt. Allerdings haben Werttypen auch ihre Nachteile. Das Umherreichen eines großen structs ist definitiv kostspieliger als das Weiterreichen eines Verweises, das ist ein offensichtliches Problem. Das andere Problem ist der Overhead, der mit Boxing/Unboxing verbunden ist. Falls Sie sich fragen, was Boxing/Unboxing bedeutet, folgen Sie diesen Links für eine gute Erklärung zu Boxing und Unboxing. Abgesehen von der Leistung gibt es Zeiten, in denen Sie einfach möchten, dass Typen Wertsemantik haben, was sehr schwierig (oder hässlich) zu implementieren wäre, wenn Referenztypen alles sind, was Sie haben. Sie sollten Werttypen nur verwenden, wenn Sie Kopiersemantik benötigen oder automatische Initialisierung benötigen, normalerweise in Arrays dieser Typen.

0 Stimmen

Das Kopieren kleiner Strukturen oder das Übergeben per Wert ist genauso günstig wie das Kopieren oder Übergeben einer Klassenreferenz oder das Übergeben von Strukturen per ref. Das Übergeben einer Struktur beliebiger Größe per ref kostet genauso viel wie das Übergeben einer Klassenreferenz per Wert. Das Kopieren einer Struktur beliebiger Größe oder das Übergeben per Wert ist günstiger als das Durchführen einer defensiven Kopie eines Klassenobjekts und Speichern oder Übergeben einer Referenz dazu. Die Zeiten, in denen Klassen besser als Strukturen sind, um Werte zu speichern, sind (1) wenn die Klassen unveränderlich sind (um das Defensivkopieren zu vermeiden) und jede Instanz, die erstellt wird, viel weitergegeben wird, oder...

0 Stimmen

...(2) wenn aus verschiedenen Gründen eine Struktur einfach nicht verwendbar wäre [z. B. weil man für etwas wie einen Baum verschachtelte Verweise benötigt oder weil man Polymorphismus benötigt]. Beachten Sie, dass beim Verwenden von Werttypen Felder im Allgemeinen direkt freigelegt werden sollten, es sei denn, es gibt keinen bestimmten Grund dafür (während bei den meisten Klassentypen Felder in Eigenschaften verpackt werden sollten). Viele der sogenannten "Übel" von veränderbaren Werttypen resultieren aus dem unnötigen Verpacken von Feldern in Eigenschaften (z. B. während einige Compiler es erlauben würden, einen Eigenschaftssetter auf einer schreibgeschützten Struktur aufzurufen, weil es manchmal...

0 Stimmen

...das Richtige zu tun, würden alle Compiler Versuche direkt abzulehnen, Felder in solchen Strukturen zu setzen; der beste Weg, um sicherzustellen, dass Compiler readOnlyStruct.someMember = 5; ablehnen, ist es, someMember nicht zu einer schreibgeschützten Eigenschaft zu machen, sondern es stattdessen zu einem Feld zu machen.

15voto

habib Punkte 2137

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

1 Stimmen

Sehr gut, wenn du richtig liegst. Auch sehr gut, eine Referenz hinzuzufügen.

15voto

leppie Punkte 111830

Mit Ausnahme der Valuetypes, die direkt vom Laufzeitumgebung verwendet werden und verschiedene andere für PInvoke-Zwecke, sollten Valuetypes nur in 2 Szenarien verwendet werden.

  1. Wenn Sie Kopiesemantik benötigen.
  2. Wenn Sie eine automatische Initialisierung benötigen, normalerweise in Arrays dieser Typen.

0 Stimmen

2 scheint ein Teil des Grunds für die strukturelle Prävalenz in .Net-Sammlungsklassen zu sein.

0 Stimmen

Wenn das erste, was man tun würde, wenn man einen Speicherort eines Klassentyps erstellt, wäre, eine neue Instanz dieses Typs zu erstellen, eine Referenz dazu an diesem Speicherort zu speichern und diese Referenz nirgendwo anders zu kopieren oder zu überschreiben, dann würden sich eine Struktur und eine Klasse identisch verhalten. Strukturen haben einen bequemen Standardweg, um alle Felder von einer Instanz in eine andere zu kopieren, und bieten im Allgemeinen bessere Leistung in Fällen, in denen man niemals eine Referenz auf eine Klasse duplizieren würde (außer für den ephemeren this Parameter, der verwendet wird, um ihre Methoden aufzurufen); Klassen ermöglichen es, Referenzen zu duplizieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X