it-swarm.dev

Wann verwenden Sie eine Struktur anstelle einer Klasse?

Was sind Ihre Faustregeln für die Verwendung von Strukturen oder Klassen? Ich denke an die C # -Definition dieser Begriffe, aber wenn Ihre Sprache ähnliche Konzepte hat, würde ich gerne auch Ihre Meinung hören.

Ich neige dazu, Klassen für fast alles zu verwenden und Strukturen nur dann zu verwenden, wenn etwas sehr simpel ist und ein Werttyp sein sollte, wie z. B. eine Telefonnummer oder ähnliches. Dies scheint jedoch eine relativ geringe Verwendung zu sein, und ich hoffe, dass es interessantere Anwendungsfälle gibt.

176
RationalGeek

Die allgemeine Regel lautet, dass Strukturen kleine, einfache (einstufige) Sammlungen verwandter Eigenschaften sein sollten, die nach ihrer Erstellung unveränderlich sind. Verwenden Sie für alles andere eine Klasse.

C # ist insofern schön, als Strukturen und Klassen keine expliziten Unterschiede in der Deklaration aufweisen, außer dem definierenden Schlüsselwort. Wenn Sie also der Meinung sind, dass Sie eine Struktur auf eine Klasse "upgraden" oder umgekehrt eine Klasse auf eine Struktur "downgraden" müssen, müssen Sie das Schlüsselwort meist einfach ändern (es gibt einige andere Fallstricke; Strukturen können nicht abgeleitet werden) von einer anderen Klasse oder einem anderen Strukturtyp, und sie können keinen standardmäßigen parameterlosen Konstruktor explizit definieren.

Ich sage "meistens", weil das Wichtigste, was man über Strukturen wissen muss, ist, dass die Behandlung wie Werttypen (Referenztypen) anderthalb Schmerzen verursachen kann, da es sich um Werttypen handelt. Insbesondere das Ändern der Eigenschaften einer Struktur kann zu unerwartetem Verhalten führen.

Angenommen, Sie haben eine Klasse SimpleClass mit zwei Eigenschaften, A und B. Sie instanziieren eine Kopie dieser Klasse, initialisieren A und B und übergeben die Instanz an eine andere Methode. Diese Methode ändert A und B. In der aufrufenden Funktion (die die Instanz erstellt hat) erhalten die A und B Ihrer Instanz die Werte, die ihnen von der aufgerufenen Methode zugewiesen wurden.

Jetzt machst du es zu einer Struktur. Die Eigenschaften sind noch veränderlich. Sie führen dieselben Operationen mit derselben Syntax wie zuvor aus, aber jetzt befinden sich die neuen Werte von A und B nach dem Aufrufen der Methode nicht in der Instanz. Was ist passiert? Nun, Ihre Klasse ist jetzt eine Struktur, was bedeutet, dass es sich um einen Werttyp handelt. Wenn Sie einen Werttyp an eine Methode übergeben, wird standardmäßig (ohne ein Schlüsselwort out oder ref) "by value" übergeben. Eine flache Kopie der Instanz wird zur Verwendung durch die Methode erstellt und dann zerstört, wenn die Methode abgeschlossen ist und die ursprüngliche Instanz intakt bleibt.

Dies wird noch verwirrender, wenn Sie einen Referenztyp als Mitglied Ihrer Struktur haben (nicht unzulässig, aber extrem schlechte Praxis in praktisch allen Fällen); Die Klasse wird nicht geklont (nur der Verweis der Struktur darauf), sodass Änderungen an der Struktur das ursprüngliche Objekt nicht beeinflussen, Änderungen an der Unterklasse der Struktur jedoch die Instanz aus dem aufrufenden Code beeinflussen. Dies kann sehr leicht veränderbare Strukturen in sehr inkonsistente Zustände versetzen, die Fehler verursachen können, weit entfernt von dem eigentlichen Problem.

Aus diesem Grund sagt praktisch jede Behörde in C #, dass Ihre Strukturen immer unveränderlich sein sollen. Ermöglichen Sie dem Verbraucher, die Werte der Eigenschaften nur bei der Erstellung eines Objekts anzugeben, und bieten Sie niemals die Möglichkeit, die Werte dieser Instanz zu ändern. Schreibgeschützte Felder oder Nur-Get-Eigenschaften sind die Regel. Wenn der Verbraucher den Wert ändern möchte, kann er ein neues Objekt basierend auf den Werten des alten mit den gewünschten Änderungen erstellen oder eine Methode aufrufen, die dasselbe tut. Dies zwingt sie, eine einzelne Instanz Ihrer Struktur als einen konzeptuellen "Wert" zu behandeln, der unteilbar ist und sich von allen anderen unterscheidet (aber möglicherweise mit diesen gleichwertig ist). Wenn sie eine Operation für einen von Ihrem Typ gespeicherten "Wert" ausführen, erhalten sie einen neuen "Wert", der sich von ihrem Anfangswert unterscheidet, aber dennoch vergleichbar und/oder semantisch gleichwertig ist.

Ein gutes Beispiel finden Sie im DateTime-Typ. Sie können keines der Felder einer DateTime-Instanz direkt zuweisen. Sie müssen entweder eine neue erstellen oder eine Methode für die vorhandene aufrufen, die eine neue Instanz erzeugt. Dies liegt daran, dass ein Datum und eine Uhrzeit wie die Zahl 5 ein "Wert" sind und eine Änderung der Zahl 5 zu einem neuen Wert führt, der nicht 5 ist. Nur weil 5 + 1 = 6 nicht bedeutet, dass 5 jetzt 6 ist weil du 1 hinzugefügt hast. DateTimes funktionieren genauso. 12:00 wird nicht zu 12:01, wenn Sie eine Minute hinzufügen, sondern Sie erhalten stattdessen einen neuen Wert 12:01, der sich von 12:00 unterscheidet. Wenn dies für Ihren Typ ein logischer Sachverhalt ist (gute konzeptionelle Beispiele, die nicht in .NET integriert sind, sind Money, Distance, Weight und andere Mengen einer UOM, bei denen Operationen alle Teile des Werts berücksichtigen müssen), Verwenden Sie dann eine Struktur und entwerfen Sie sie entsprechend. Verwenden Sie in den meisten anderen Fällen, in denen die Unterelemente eines Objekts unabhängig voneinander veränderbar sein sollten, eine Klasse.

170
KeithS

Die Antwort: "Verwenden Sie eine Struktur für reine Datenkonstrukte und eine Klasse für Objekte mit Operationen" ist IMO definitiv falsch. Wenn eine Struktur eine große Anzahl von Eigenschaften enthält, ist eine Klasse fast immer besser geeignet. Microsoft sagt aus Effizienzgründen oft, wenn Ihr Typ größer als 16 Byte ist, sollte er eine Klasse sein.

https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes

14
bytedev

Der wichtigste Unterschied zwischen einem class und einem struct ist, was in der folgenden Situation passiert:

 Sache thing1 = new Thing (); 
 Ding1.somePropertyOrField = 5; 
 Sache thing2 = thing1; 
 Ding2.somePropertyOrField = 9; 

Wie sollte sich die letzte Anweisung auf thing1.somePropertyOrField Auswirken? Wenn Thing eine Struktur ist und somePropertyOrField ein offengelegtes öffentliches Feld ist, werden die Objekte thing1 Und thing2 Voneinander "getrennt", so dass letztere Anweisung hat keinen Einfluss auf thing1. Wenn Thing eine Klasse ist, werden thing1 Und thing2 Aneinander angehängt, und die letztere Anweisung schreibt thing1.somePropertyOrField. Man sollte eine Struktur in Fällen verwenden, in denen die erstere Semantik sinnvoller wäre, und eine Klasse in Fällen, in denen die letztere Semantik sinnvoller wäre.

Beachten Sie, dass einige Leute zwar raten, dass der Wunsch, etwas veränderlich zu machen, ein Argument dafür ist, dass es die Klasse ist, aber ich würde vorschlagen, dass das Gegenteil der Fall ist: Wenn etwas, das zum Zweck des Haltens einiger Daten existiert, veränderlich sein wird, und Wenn es nicht offensichtlich ist, ob Instanzen an irgendetwas angehängt sind, sollte das Ding eine Struktur sein (wahrscheinlich mit exponierten Feldern), um zu verdeutlichen, dass Instanzen an nichts anderes angehängt sind.

Betrachten Sie zum Beispiel die Aussagen:

 Person somePerson = myPeople.GetPerson ("123-45-6789"); 
 SomePerson.Name = "Mary Johnson"; // War "Mary Smith" 

Wird die zweite Anweisung die in myPeople gespeicherten Informationen ändern? Wenn Person eine exponierte Feldstruktur ist, wird dies nicht der Fall sein. und die Tatsache, dass es keine offensichtliche Folge davon sein wird, dass es eine exponierte Feldstruktur ist;; Wenn Person eine Struktur ist und man myPeople aktualisieren möchte, müsste man eindeutig so etwas wie myPeople.UpdatePerson("123-45-6789", somePerson) tun. Wenn Person eine Klasse ist, kann es jedoch viel schwieriger sein zu bestimmen, ob der obige Code den Inhalt von MyPeople niemals aktualisieren, immer aktualisieren oder manchmal aktualisieren würde.

In Bezug auf die Vorstellung, dass Strukturen "unveränderlich" sein sollten, stimme ich im Allgemeinen nicht zu. Es gibt gültige Anwendungsfälle für "unveränderliche" Strukturen (bei denen Invarianten in einem Konstruktor erzwungen werden), aber die Anforderung, dass eine gesamte Struktur jedes Mal neu geschrieben werden muss, wenn sich ein Teil davon ändert, ist umständlich, verschwenderisch und kann eher Fehler verursachen als nur das Aufdecken Felder direkt. Stellen Sie sich zum Beispiel eine PhoneNumber -Struktur vor, deren Felder unter anderem AreaCode und Exchange enthalten, und nehmen Sie an, dass eine List<PhoneNumber> Hat. Die Wirkung der folgenden Punkte sollte ziemlich klar sein:

 für (int i = 0; i <myList.Count; i ++) 
 {
 PhoneNumber theNumber = myList [i]; 
 if (theNumber.AreaCode = = "312") 
 {
 String newExchange = ""; 
 If (new312to708Exchanges.TryGetValue (theNumber.Exchange), out newExchange) 
 {
 theNumber.AreaCode = "708"; 
 theNumber.Exchange = newExchange; 
 myList [i] = theNumber; 
} 
} 
 } 

Beachten Sie, dass nichts im obigen Code Felder in PhoneNumber außer AreaCode und Exchange kennt oder sich darum kümmert. Wenn PhoneNumber eine sogenannte "unveränderliche" Struktur wäre, müsste entweder eine withXX -Methode für jedes Feld bereitgestellt werden, die eine neue Strukturinstanz zurückgibt, die die übergebene Struktur enthält Wert im angegebenen Feld, sonst müsste Code wie der oben genannte über jedes Feld in der Struktur Bescheid wissen. Nicht gerade ansprechend.

Übrigens gibt es mindestens zwei Fälle, in denen Strukturen vernünftigerweise Verweise auf veränderbare Typen enthalten können.

  1. Die Semantik der Struktur zeigt an, dass sie die Identität des betreffenden Objekts speichert und nicht als Verknüpfung zum Speichern der Objekteigenschaften. Beispielsweise würde ein "KeyValuePair" die Identitäten bestimmter Schaltflächen enthalten, es wird jedoch nicht erwartet, dass es dauerhafte Informationen über die Positionen, Hervorhebungszustände usw. dieser Schaltflächen enthält.
  2. Die Struktur weiß, dass sie den einzigen Verweis auf dieses Objekt enthält, niemand anderes erhält einen Verweis, und alle Mutationen, die jemals an diesem Objekt durchgeführt werden, wurden durchgeführt, bevor ein Verweis darauf irgendwo gespeichert wird.

Im ersteren Szenario ist die IDENTITÄT des Objekts unveränderlich. Im zweiten Fall erzwingt die Klasse des verschachtelten Objekts möglicherweise keine Unveränderlichkeit, aber die Struktur, die die Referenz enthält, wird dies tun.

10
supercat

Ich neige dazu, Strukturen nur zu verwenden, wenn eine Methode benötigt wird, wodurch eine Klasse zu "schwer" erscheint. Daher behandle ich Strukturen manchmal als leichte Objekte. ACHTUNG: Dies funktioniert nicht immer und ist nicht immer das Beste, also hängt es wirklich von der Situation ab!

ZUSÄTZLICHE RESSOURCEN

Für eine formellere Erklärung sagt MSDN: http://msdn.Microsoft.com/en-us/library/ms229017.aspx Dieser Link überprüft, was ich oben erwähnt habe. Strukturen sollten nur für die Unterbringung von a verwendet werden kleine Datenmenge.

7
rrazd

Verwenden Sie eine Struktur für reine Datenkonstrukte und eine Klasse für Objekte mit Operationen.

Wenn Ihre Datenstruktur keine Zugriffssteuerung benötigt und keine anderen speziellen Operationen als get/set hat, verwenden Sie eine Struktur. Dies macht deutlich, dass diese Struktur lediglich ein Container für Daten ist.

Wenn Ihre Struktur Operationen enthält, die die Struktur auf komplexere Weise ändern, verwenden Sie eine Klasse. Eine Klasse kann auch über Argumente zu Methoden mit anderen Klassen interagieren.

Betrachten Sie es als den Unterschied zwischen einem Ziegelstein und einem Flugzeug. Ein Ziegelstein ist eine Struktur; es hat Länge, Breite und Höhe. Es kann nicht viel tun. Ein Flugzeug kann mehrere Operationen haben, die es irgendwie mutieren, wie das Bewegen von Steuerflächen und das Ändern des Triebwerksschubs. Als Klasse wäre es besser.

2
Michael K