Wann sollten Sie struct und nicht class in C # verwenden? Mein konzeptionelles Modell ist, dass Strukturen in Zeiten verwendet werden, in denen das Element lediglich eine Sammlung von Werttypen ist . Eine Möglichkeit, sie alle logisch zu einem zusammenhängenden Ganzen zusammenzuhalten.
Ich bin hier auf diese Regeln gestoßen :
- Eine Struktur sollte einen einzelnen Wert darstellen.
- Eine Struktur sollte einen Speicherbedarf von weniger als 16 Byte haben.
- Eine Struktur sollte nach der Erstellung nicht geändert werden.
Funktionieren diese Regeln? Was bedeutet eine Struktur semantisch?
System.Drawing.Rectangle
verstößt gegen alle drei Regeln.Antworten:
Die Quelle, auf die das OP verweist, hat eine gewisse Glaubwürdigkeit ... aber was ist mit Microsoft - wie steht es zur Strukturnutzung? Ich habe mich bei Microsoft um zusätzliche Informationen bemüht und Folgendes gefunden:
Microsoft verstößt konsequent gegen diese Regeln
Okay, # 2 und # 3 sowieso. Unser geliebtes Wörterbuch hat 2 interne Strukturen:
* Referenzquelle
Die Quelle 'JonnyCantCode.com' erhielt 3 von 4 Punkten - ziemlich verzeihlich, da # 4 wahrscheinlich kein Problem darstellen würde. Wenn Sie feststellen, dass Sie eine Struktur boxen, überdenken Sie Ihre Architektur.
Schauen wir uns an, warum Microsoft diese Strukturen verwenden würde:
Entry
undEnumerator
repräsentiert einzelne Werte.Entry
wird niemals als Parameter außerhalb der Dictionary-Klasse übergeben. Weitere Untersuchungen zeigen, dass Dictionary zur Erfüllung der Implementierung von IEnumerable dieEnumerator
Struktur verwendet, die es jedes Mal kopiert, wenn ein Enumerator angefordert wird ... sinnvoll ist.Enumerator
ist öffentlich, da Dictionary aufzählbar ist und den gleichen Zugriff auf die Implementierung der IEnumerator-Schnittstelle haben muss - z. B. IEnumerator getter.Aktualisieren - Beachten Sie außerdem, dass, wenn eine Struktur - wie Enumerator - eine Schnittstelle implementiert und in diesen implementierten Typ umgewandelt wird, die Struktur zu einem Referenztyp wird und in den Heap verschoben wird. Enumerator ist in der Dictionary-Klasse immer noch ein Werttyp. Sobald jedoch eine Methode aufgerufen wird
GetEnumerator()
, wird ein ReferenztypIEnumerator
zurückgegeben.Was wir hier nicht sehen, ist ein Versuch oder ein Beweis für die Anforderung, Strukturen unveränderlich zu halten oder eine Instanzgröße von nur 16 Bytes oder weniger beizubehalten:
readonly
- nicht unveränderlichEntry
hat eine unbestimmte Lebensdauer (vonAdd()
, aufRemove()
,Clear()
oder garbage collection);Und ... 4. Beide Strukturen speichern TKey und TValue, von denen wir alle wissen, dass sie durchaus Referenztypen sein können (zusätzliche Bonusinformationen).
Ungeachtet der Hashed Keys sind Wörterbücher teilweise schnell, da das Instanziieren einer Struktur schneller ist als ein Referenztyp. Hier habe ich eine
Dictionary<int, int>
, die 300.000 zufällige Ganzzahlen mit sequentiell inkrementierten Schlüsseln speichert.Kapazität : Anzahl der verfügbaren Elemente, bevor die Größe des internen Arrays geändert werden muss.
MemSize : Wird ermittelt, indem das Wörterbuch in einen MemoryStream serialisiert und eine Bytelänge ermittelt wird (genau genug für unsere Zwecke).
Abgeschlossene Größenänderung : Die Zeit, die benötigt wird, um die Größe des internen Arrays von 150862 Elementen auf 312874 Elemente zu ändern. Wenn Sie sich vorstellen, dass jedes Element nacheinander kopiert
Array.CopyTo()
wird, ist das nicht allzu schäbig.Gesamtzeit zum Füllen : Zugegebenermaßen aufgrund der Protokollierung und eines
OnResize
Ereignisses, das ich der Quelle hinzugefügt habe, verzerrt ; Es ist jedoch immer noch beeindruckend, 300.000 Ganzzahlen zu füllen, während die Größe während des Vorgangs 15 Mal geändert wird. Was wäre die Gesamtzeit aus Neugier, wenn ich die Kapazität bereits kennen würde? 13msWas wäre
Entry
nun eine Klasse? Würden sich diese Zeiten oder Metriken wirklich so stark unterscheiden?Offensichtlich liegt der große Unterschied in der Größenänderung. Gibt es einen Unterschied, wenn das Wörterbuch mit der Kapazität initialisiert wird? Nicht genug, um sich mit ... 12ms zu befassen .
Da
Entry
es sich um eine Struktur handelt, ist keine Initialisierung wie bei einem Referenztyp erforderlich. Dies ist sowohl die Schönheit als auch der Fluch des Werttyps. UmEntry
als Referenztyp zu verwenden, musste ich den folgenden Code einfügen:Der Grund, warum ich jedes Array-Element
Entry
als Referenztyp initialisieren musste, finden Sie unter MSDN: Structure Design . Zusamenfassend:Es ist eigentlich ganz einfach und wir werden uns Asimovs Drei Gesetze der Robotik leihen :
... was nehmen wir davon weg : Kurz gesagt, seien Sie verantwortlich für die Verwendung von Werttypen. Sie sind schnell und effizient, können jedoch viele unerwartete Verhaltensweisen verursachen, wenn sie nicht ordnungsgemäß gewartet werden (dh unbeabsichtigte Kopien).
quelle
Decimal
oderDateTime
], sollte sie durch eine Klasse ersetzt werden, wenn sie die anderen drei Regeln nicht einhält. Wenn eine Struktur eine feste Sammlung von Variablen enthält, von denen jede einen beliebigen Wert enthalten kann, der für ihren Typ gültig wäre [z. B.Rectangle
], sollte sie unterschiedliche Regeln einhalten , von denen einige denen für "Einzelwert" -Strukturen widersprechen .Dictionary
Eintragstyp damit begründen, dass es sich nur um einen internen Typ handelt, die Leistung als wichtiger angesehen wurde als die Semantik oder eine andere Ausrede. Mein Punkt ist, dass ein Typ wieRectangle
sein Inhalt als individuell bearbeitbare Felder angezeigt werden sollte, nicht "weil" die Leistungsvorteile die resultierenden semantischen Unvollkommenheiten überwiegen, sondern weil der Typ semantisch einen festen Satz unabhängiger Werte darstellt und daher die veränderbare Struktur beides ist performanter und semantisch überlegen .Wann immer du:
Die Einschränkung besteht jedoch darin, dass die Weitergabe von Strukturen (beliebig groß) teurer ist als Klassenreferenzen (normalerweise ein Maschinenwort), sodass Klassen in der Praxis möglicherweise schneller sind.
quelle
(Guid)null
(es ist in Ordnung, eine Null in einen Referenztyp umzuwandeln).Ich bin mit den im ursprünglichen Beitrag angegebenen Regeln nicht einverstanden. Hier sind meine Regeln:
1) Sie verwenden Strukturen für die Leistung, wenn sie in Arrays gespeichert sind. (siehe auch Wann sind Strukturen die Antwort? )
2) Sie benötigen sie für den Code, der strukturierte Daten an / von C / C ++ übergibt
3) Verwenden Sie keine Strukturen, es sei denn, Sie benötigen sie:
quelle
struct
zu wissen, wie es sich verhält, aber wenn etwas einstruct
mit exponierten Feldern ist, ist das alles, was man wissen muss. Wenn ein Objekt eine Eigenschaft eines Typs für exponierte Feldstrukturen verfügbar macht und Code diese Struktur in eine Variable liest und ändert, kann man sicher vorhersagen, dass eine solche Aktion das Objekt, dessen Eigenschaft gelesen wurde, nicht beeinflusst, es sei denn oder bis die Struktur geschrieben wird zurück. Wenn die Eigenschaft dagegen ein veränderlicher Klassentyp wäre, könnte das Lesen und Ändern das zugrunde liegende Objekt wie erwartet aktualisieren, aber ...Verwenden Sie eine Struktur, wenn Sie eine Wertsemantik anstelle einer Referenzsemantik wünschen.
Bearbeiten
Ich bin mir nicht sicher, warum die Leute dies ablehnen, aber dies ist ein gültiger Punkt, der vor der Klärung seiner Frage gemacht wurde und der grundlegendste Grund für eine Struktur ist.
Wenn Sie Referenzsemantik benötigen, benötigen Sie eine Klasse, keine Struktur.
quelle
Neben der Antwort "Es ist ein Wert" besteht ein spezielles Szenario für die Verwendung von Strukturen darin , dass Sie wissen, dass Sie über eine Reihe von Daten verfügen , die Probleme bei der Speicherbereinigung verursachen, und dass Sie viele Objekte haben. Zum Beispiel eine große Liste / ein Array von Personeninstanzen. Die natürliche Metapher hier ist eine Klasse, aber wenn Sie eine große Anzahl langlebiger Personeninstanzen haben, können diese GEN-2 verstopfen und GC-Stände verursachen. Wenn das Szenario dies rechtfertigt, besteht ein möglicher Ansatz darin, ein Array (keine Liste) von Personenstrukturen zu verwenden , d
Person[]
. H. Anstatt Millionen von Objekten in GEN-2 zu haben, haben Sie jetzt einen einzelnen Block auf dem LOH (ich gehe hier von keinen Zeichenfolgen usw. aus - dh einem reinen Wert ohne Referenzen). Dies hat nur sehr geringe Auswirkungen auf die GC.Das Arbeiten mit diesen Daten ist umständlich, da die Daten für eine Struktur wahrscheinlich übergroß sind und Sie nicht ständig Fettwerte kopieren möchten. Wenn Sie jedoch direkt in einem Array darauf zugreifen, wird die Struktur nicht kopiert - sie ist vorhanden (im Gegensatz zu einem Listenindexer, der kopiert). Dies bedeutet viel Arbeit mit Indizes:
Beachten Sie, dass es hier hilfreich ist, die Werte selbst unveränderlich zu halten. Verwenden Sie für komplexere Logik eine Methode mit einem by-ref-Parameter:
Auch dies ist vorhanden - wir haben den Wert nicht kopiert.
In sehr spezifischen Szenarien kann diese Taktik sehr erfolgreich sein. Es ist jedoch ein ziemlich fortgeschrittenes Szenario, das nur versucht werden sollte, wenn Sie wissen, was Sie tun und warum. Der Standardwert hier wäre eine Klasse.
quelle
List
Ich glaube, verwendet einenArray
Blick hinter die Kulissen. Nein ?Aus der C # -Sprachenspezifikation :
Eine Alternative besteht darin, Point zu einer Struktur zu machen.
Jetzt wird nur ein Objekt instanziiert - das für das Array - und die Point-Instanzen werden inline im Array gespeichert.
Strukturkonstruktoren werden mit dem neuen Operator aufgerufen, dies bedeutet jedoch nicht, dass Speicher zugewiesen wird. Anstatt ein Objekt dynamisch zuzuweisen und einen Verweis darauf zurückzugeben, gibt ein Strukturkonstruktor einfach den Strukturwert selbst zurück (normalerweise an einem temporären Speicherort auf dem Stapel), und dieser Wert wird dann nach Bedarf kopiert.
Bei Klassen ist es möglich, dass zwei Variablen auf dasselbe Objekt verweisen, und dass Operationen an einer Variablen das Objekt beeinflussen, auf das die andere Variable verweist. Bei Strukturen haben die Variablen jeweils eine eigene Kopie der Daten, und es ist nicht möglich, dass Operationen an einer die andere beeinflussen. Die vom folgenden Codefragment erzeugte Ausgabe hängt beispielsweise davon ab, ob Point eine Klasse oder eine Struktur ist.
Wenn Point eine Klasse ist, ist die Ausgabe 20, da a und b auf dasselbe Objekt verweisen. Wenn Point eine Struktur ist, ist die Ausgabe 10, da durch die Zuweisung von a zu b eine Kopie des Werts erstellt wird und diese Kopie von der nachfolgenden Zuweisung zu ax nicht betroffen ist
Das vorherige Beispiel zeigt zwei der Einschränkungen von Strukturen. Erstens ist das Kopieren einer gesamten Struktur in der Regel weniger effizient als das Kopieren einer Objektreferenz, sodass die Zuweisung und Übergabe von Wertparametern bei Strukturen teurer sein kann als bei Referenztypen. Zweitens ist es mit Ausnahme der Parameter ref und out nicht möglich, Verweise auf Strukturen zu erstellen, wodurch deren Verwendung in einer Reihe von Situationen ausgeschlossen wird.
quelle
ref
einer veränderlichen Struktur sicher eine externe Methode a geben und wissen, dass alle Mutationen, die diese externe Methode auf ihr ausführt, durchgeführt werden, bevor sie zurückkehren. Es ist schade, dass .net kein Konzept für kurzlebige Parameter und Funktionsrückgabewerte hat, da ...ref
mit Klassenobjekten erreicht werden kann. Im Wesentlichen können lokale Variablen, Parameter und Funktionsrückgabewerte dauerhaft (Standard), zurückgabbar oder kurzlebig sein. Es wäre dem Code verboten, kurzlebige Dinge in alles zu kopieren, was den gegenwärtigen Umfang überleben würde. Mehrwegartikel wären wie vergängliche Dinge, außer dass sie von einer Funktion zurückgegeben werden könnten. Der Rückgabewert einer Funktion würde an die strengsten Einschränkungen gebunden sein, die für einen ihrer "rückgabbaren" Parameter gelten.Strukturen eignen sich zur atomaren Darstellung von Daten, wobei diese Daten vom Code mehrfach kopiert werden können. Das Klonen eines Objekts ist im Allgemeinen teurer als das Kopieren einer Struktur, da der Speicher zugewiesen, der Konstruktor ausgeführt und die Freigabe / Speicherbereinigung freigegeben wird, wenn dies erledigt ist.
quelle
Hier ist eine Grundregel.
Wenn alle Mitgliedsfelder Werttypen sind, erstellen Sie eine Struktur .
Wenn ein Elementfeld ein Referenztyp ist, erstellen Sie eine Klasse . Dies liegt daran, dass das Feld für den Referenztyp ohnehin die Heap-Zuordnung benötigt.
Beispiele
quelle
string
sind semantisch äquivalent zu Werten, und das Speichern einer Referenz auf ein unveränderliches Objekt in einem Feld erfordert keine Heap-Zuordnung. Der Unterschied zwischen einer Struktur mit sichtbaren öffentlichen Bereichen und ein Klassenobjekt mit sichtbaren öffentlichen Bereichen ist , dass die Codefolge gegebenvar q=p; p.X=4; q.X=5;
,p.X
den Wert 4 hat , wenna
ein Strukturtyp ist, und 5 , wenn es dich um einen Klassentyp ist. Wenn man die Elemente des Typs bequem ändern möchte, sollte man "Klasse" oder "Struktur" auswählen, je nachdem, ob Änderungenq
wirksam werden sollenp
.ArraySegment<T>
kapselt aT[]
, was immer ein Klassentyp ist. Der StrukturtypKeyValuePair<TKey,TValue>
wird häufig mit Klassentypen als generische Parameter verwendet.Erstens: Interop-Szenarien oder wenn Sie das Speicherlayout angeben müssen
Zweitens: Wenn die Daten sowieso fast die gleiche Größe wie ein Referenzzeiger haben.
quelle
Sie müssen eine "Struktur" in Situationen verwenden, in denen Sie das Speicherlayout mithilfe des StructLayoutAttribute explizit angeben möchten - normalerweise für PInvoke.
Bearbeiten: Kommentar weist darauf hin, dass Sie mit StructLayoutAttribute Klasse oder Struktur verwenden können, und das ist sicherlich richtig. In der Praxis verwenden Sie normalerweise eine Struktur - sie wird auf dem Stapel gegenüber dem Heap zugewiesen. Dies ist sinnvoll, wenn Sie nur ein Argument an einen nicht verwalteten Methodenaufruf übergeben.
quelle
Ich benutze Strukturen zum Packen oder Entpacken von binären Kommunikationsformaten. Dies umfasst das Lesen oder Schreiben auf die Festplatte, DirectX-Scheitelpunktlisten, Netzwerkprotokolle oder den Umgang mit verschlüsselten / komprimierten Daten.
Die drei Richtlinien, die Sie auflisten, waren in diesem Zusammenhang für mich nicht hilfreich. Wenn ich vierhundert Bytes in einer bestimmten Reihenfolge schreiben muss, werde ich eine vierhundert-Byte-Struktur definieren und sie mit allen nicht verwandten Werten füllen, die sie haben soll, und ich werde gehen es so einzurichten, wie es zu der Zeit am sinnvollsten ist. (Okay, vierhundert Bytes wären ziemlich seltsam - aber als ich meinen Lebensunterhalt mit Excel-Dateien verdient habe, habe ich mich mit Strukturen von bis zu vierzig Bytes beschäftigt, denn so groß sind einige der BIFF-Datensätze.)
quelle
Mit Ausnahme der Werttypen, die direkt von der Laufzeit und verschiedenen anderen für PInvoke-Zwecke verwendet werden, sollten Sie Werttypen nur in zwei Szenarien verwenden.
quelle
this
Parameters, der zum Aufrufen ihrer Methoden verwendet wird). Klassen ermöglichen das Duplizieren von Referenzen..NET unterstützt
value types
undreference types
(in Java können Sie nur Referenztypen definieren). Instanzen vonreference types
werden im verwalteten Heap zugewiesen und werden mit Müll gesammelt, wenn keine ausstehenden Verweise darauf vorhanden sind. Instanzen vonvalue types
werden andererseits im zugeordnetstack
, und daher wird der zugewiesene Speicher zurückgefordert, sobald ihr Umfang endet. Und natürlich werdenvalue types
Sie nach Wert undreference types
Referenz übergeben. Alle primitiven C # -Datentypen mit Ausnahme von System.String sind Werttypen.Wann wird struct over class verwendet?
In C #
structs
sindvalue types
Klassenreference types
. Sie können Werttypen in C # mit demenum
Schlüsselwort und demstruct
Schlüsselwort erstellen . Die Verwendung von avalue type
anstelle von areference type
führt zu weniger Objekten auf dem verwalteten Heap, was zu einer geringeren Belastung des Garbage Collector (GC), weniger häufigen GC-Zyklen und folglich zu einer besseren Leistung führt. Allerdingsvalue types
haben auch ihre Schattenseiten. Dasstruct
Weitergeben eines großen ist definitiv teurer als das Weitergeben einer Referenz, das ist ein offensichtliches Problem. Das andere Problem ist der damit verbundene Overheadboxing/unboxing
. Wenn Sie sich fragen, was dasboxing/unboxing
bedeutet, folgen Sie diesen Links, um eine gute Erklärung zuboxing
und zu erhaltenunboxing
. Abgesehen von der Leistung gibt es Zeiten, in denen Sie einfach Typen benötigen, um eine Wertsemantik zu haben, deren Implementierung sehr schwierig (oder hässlich) wäre, wenn Sie nur über eine solchereference types
verfügen. Sie solltenvalue types
nur verwenden, wenn Sie eine Kopiersemantik oder eine automatische Initialisierung benötigen, normalerweise inarrays
diesen Typen.quelle
ref
. Das Übergeben einer beliebigen Größenstrukturref
kostet genauso viel wie das Übergeben einer Klassenreferenz nach Wert. Das Kopieren einer Struktur beliebiger Größe oder das Übergeben von Werten ist billiger als das Ausführen einer defensiven Kopie eines Klassenobjekts und das Speichern oder Übergeben eines Verweises darauf. Die großen Zeiten, in denen Klassen besser sind als Strukturen zum Speichern von Werten, sind (1), wenn die Klassen unveränderlich sind (um defensives Kopieren zu vermeiden), und jede erstellte Instanz wird vielreadOnlyStruct.someMember = 5;
besteht darin,someMember
keine schreibgeschützte Eigenschaft zu erstellen, sondern sie zu einem Feld zu machen.Eine Struktur ist ein Werttyp. Wenn Sie einer neuen Variablen eine Struktur zuweisen, enthält die neue Variable eine Kopie des Originals.
Die Ausführung der folgenden Ergebnisse führt zu 5 Instanzen der im Speicher gespeicherten Struktur:
Eine Klasse ist ein Referenztyp. Wenn Sie einer neuen Variablen eine Klasse zuweisen, enthält die Variable einen Verweis auf das ursprüngliche Klassenobjekt.
Die Ausführung des Folgenden führt nur zu einer Instanz des Klassenobjekts im Speicher.
Strukturen können die Wahrscheinlichkeit eines Codefehlers erhöhen. Wenn ein Wertobjekt wie ein veränderbares Referenzobjekt behandelt wird, kann ein Entwickler überrascht sein, wenn die vorgenommenen Änderungen unerwartet verloren gehen.
quelle
Ich habe mit BenchmarkDotNet einen kleinen Benchmark erstellt , um den "Struktur" -Vorteil in Zahlen besser zu verstehen. Ich teste das Durchlaufen eines Arrays (oder einer Liste) von Strukturen (oder Klassen). Das Erstellen dieser Arrays oder Listen liegt außerhalb des Bereichs des Benchmarks. Es ist klar, dass "Klasse" schwerer ist, mehr Speicher benötigt und GC umfasst.
Die Schlussfolgerung lautet also: Seien Sie vorsichtig mit LINQ und versteckten Strukturen beim Boxen / Entpacken und verwenden Sie Strukturen für Mikrooptimierungen ausschließlich bei Arrays.
PS Ein weiterer Maßstab für die Übergabe von Struktur / Klasse durch den Aufrufstapel ist https://stackoverflow.com/a/47864451/506147
Code:
quelle
Strukturtypen in C # oder anderen .net-Sprachen werden im Allgemeinen verwendet, um Dinge zu speichern, die sich wie Wertegruppen mit fester Größe verhalten sollten. Ein nützlicher Aspekt von Strukturtypen besteht darin, dass die Felder einer Strukturtypinstanz geändert werden können, indem der Speicherort, in dem sie gespeichert ist, geändert wird, und auf keine andere Weise. Es ist möglich, eine Struktur so zu codieren, dass die einzige Möglichkeit, ein Feld zu mutieren, darin besteht, eine ganz neue Instanz zu erstellen und dann mithilfe einer Strukturzuweisung alle Felder des Ziels zu mutieren, indem sie mit Werten aus der neuen Instanz überschrieben werden Sofern eine Struktur keine Möglichkeit bietet, eine Instanz zu erstellen, in der ihre Felder nicht standardmäßige Werte haben, sind alle Felder veränderbar, wenn die Struktur selbst an einem veränderlichen Speicherort gespeichert ist.
Beachten Sie, dass es möglich ist, einen Strukturtyp so zu gestalten, dass er sich im Wesentlichen wie ein Klassentyp verhält, wenn die Struktur ein privates Feld vom Typ Klasse enthält und seine eigenen Elemente zu dem des umschlossenen Klassenobjekts umleitet. Zum Beispiel
PersonCollection
könnte a Eigenschaften anbietenSortedByName
undSortedById
, die beide einen "unveränderlichen" Verweis auf a enthaltenPersonCollection
(in ihrem Konstruktor festgelegt) undGetEnumerator
durch Aufrufen von entwedercreator.GetNameSortedEnumerator
oder implementierencreator.GetIdSortedEnumerator
. Solche Strukturen würden sich ähnlich wie ein Verweis auf a verhaltenPersonCollection
, außer dass ihreGetEnumerator
Methoden an verschiedene Methoden in der gebunden wärenPersonCollection
. Man könnte auch eine Struktur einen Teil eines Arrays umschließen lassen (z. B. könnte man eineArrayRange<T>
Struktur definieren , die einenT[]
aufgerufenenArr
, einen int enthältOffset
und einen int enthältLength
mit einer indizierten Eigenschaft, auf die für einen Indexidx
im Bereich von 0 bisLength-1
zugegriffen werden würdeArr[idx+Offset]
). Wennfoo
es sich um eine schreibgeschützte Instanz einer solchen Struktur handelt, erlauben aktuelle Compilerversionen leider keine Operationen wie,foo[3]+=4;
da sie nicht bestimmen können, ob solche Operationen versuchen würden, in Felder von zu schreibenfoo
.Es ist auch möglich, eine Struktur so zu entwerfen, dass sie sich wie ein Werttyp verhält, der eine Sammlung mit variabler Größe enthält (die scheinbar immer dann kopiert wird, wenn sich die Struktur befindet). Die einzige Möglichkeit, dies zu erreichen, besteht darin, sicherzustellen, dass kein Objekt vorhanden ist, für das die struct enthält eine Referenz, die jemals irgendetwas ausgesetzt sein wird, das sie mutieren könnte. Zum Beispiel könnte man eine Array-ähnliche Struktur haben, die ein privates Array enthält und deren indizierte "put" -Methode ein neues Array erstellt, dessen Inhalt bis auf ein geändertes Element dem des Originals entspricht. Leider kann es etwas schwierig sein, solche Strukturen effizient arbeiten zu lassen. Es gibt zwar Zeiten, in denen die Struktur-Semantik praktisch sein kann (z. B. die Möglichkeit, eine Array-ähnliche Sammlung an eine Routine zu übergeben, wobei sowohl der Aufrufer als auch der Angerufene wissen, dass externer Code die Sammlung nicht ändert).
quelle
Nein, ich stimme den Regeln nicht ganz zu. Sie sind gute Richtlinien, die bei Leistung und Standardisierung zu berücksichtigen sind, jedoch nicht im Hinblick auf die Möglichkeiten.
Wie Sie in den Antworten sehen können, gibt es viele kreative Möglichkeiten, sie zu verwenden. Diese Richtlinien müssen also genau das sein, immer aus Gründen der Leistung und Effizienz.
In diesem Fall verwende ich Klassen, um Objekte der realen Welt in ihrer größeren Form darzustellen. Ich verwende Strukturen, um kleinere Objekte darzustellen, die genauere Verwendungszwecke haben. So wie du es gesagt hast, "ein zusammenhängenderes Ganzes". Das Schlüsselwort ist zusammenhängend. Die Klassen sind eher objektorientierte Elemente, während Strukturen einige dieser Eigenschaften aufweisen können, wenn auch in kleinerem Maßstab. IMO.
Ich verwende sie häufig in Treeview- und Listview-Tags, auf die sehr schnell auf allgemeine statische Attribute zugegriffen werden kann. Ich habe immer versucht, diese Informationen auf andere Weise zu erhalten. In meinen Datenbankanwendungen verwende ich beispielsweise eine Baumansicht, in der ich Tabellen, SPs, Funktionen oder andere Objekte habe. Ich erstelle und fülle meine Struktur, füge sie in das Tag ein, ziehe sie heraus, erhalte die Daten der Auswahl und so weiter. Ich würde das nicht mit einer Klasse machen!
Ich versuche, sie klein zu halten, sie in Einzelsituationen zu verwenden und zu verhindern, dass sie sich ändern. Es ist ratsam, sich des Speichers, der Zuordnung und der Leistung bewusst zu sein. Und das Testen ist so notwendig.
quelle
double
Werten für diese Koordinaten zu erstellen, würde eine solche Spezifikation sie dazu zwingen Verhalten Sie sich semantisch identisch mit einer exponierten Feldstruktur, mit Ausnahme einiger Details des Multithread-Verhaltens (die unveränderliche Klasse wäre in einigen Fällen besser, während die exponierte Feldstruktur in anderen Fällen besser wäre; eine sogenannte "unveränderliche" Struktur würde dies tun in jedem Fall schlechter sein).Meine Regel ist
1, benutze immer Klasse;
2, Wenn es ein Leistungsproblem gibt, versuche ich, eine Klasse in Abhängigkeit von den von @IAbstract erwähnten Regeln in struct zu ändern, und mache dann einen Test, um festzustellen, ob diese Änderungen die Leistung verbessern können.
quelle
Foo
eine feste Sammlung unabhängiger Werte (z. B. Koordinaten eines Punkts) kapseln soll, die manchmal als Gruppe weitergegeben und manchmal unabhängig geändert werden soll. Ich habe kein Muster für die Verwendung von Klassen gefunden, das beide Zwecke fast so gut kombiniert wie eine einfache Exposed-Field-Struktur (die als feste Sammlung unabhängiger Variablen perfekt zur Rechnung passt).public readonly
MyListOfPoint[3].Offset(2,3);
invar temp=MyListOfPoint[3]; temp.Offset(2,3);
eine Transformation, die bei Anwendung falsch ist ...Offset
Methode. Der richtige Weg, um solchen falschen Code zu verhindern, sollte nicht darin bestehen, Strukturen unnötig unveränderlich zu machen, sondern Methoden zuzulassenOffset
, die mit einem Attribut versehen werden sollen, das die oben erwähnte Transformation verbietet. Auch implizite numerische Konvertierungen hätten viel besser sein können, wenn sie so markiert worden wären, dass sie nur in Fällen anwendbar sind, in denen ihr Aufruf offensichtlich wäre. Wenn fürfoo(float,float)
und Überladungen vorhanden sindfoo(double,double)
, würde ich davon ausgehen, dass der Versuch, afloat
und a zu verwenden,double
häufig keine implizite Konvertierung anwenden sollte, sondern stattdessen ein Fehler sein sollte.double
Werts zu afloat
oder die Übergabe an eine Methode, die einfloat
Argument annehmen kann, aber nichtdouble
, würde fast immer das tun, was der Programmierer beabsichtigt hat. Im Gegensatz dazu ist das Zuweisen einesfloat
Ausdrucksdouble
ohne explizite Typumwandlung oft ein Fehler. Die implizitedouble->float
Konvertierung würde nur dann zu Problemen führen, wenn eine weniger als ideale Überlastung ausgewählt würde. Ich würde davon ausgehen, dass der richtige Weg, dies zu verhindern, nicht darin bestehen sollte, implizites Double-> Float zu verbieten, sondern Überladungen mit Attributen zu versehen, um die Konvertierung nicht zuzulassen.Eine Klasse ist ein Referenztyp. Wenn ein Objekt der Klasse erstellt wird, enthält die Variable, der das Objekt zugewiesen ist, nur einen Verweis auf diesen Speicher. Wenn die Objektreferenz einer neuen Variablen zugewiesen wird, bezieht sich die neue Variable auf das ursprüngliche Objekt. Über eine Variable vorgenommene Änderungen werden in der anderen Variablen wiedergegeben, da beide auf dieselben Daten verweisen. Eine Struktur ist ein Werttyp. Wenn eine Struktur erstellt wird, enthält die Variable, der die Struktur zugewiesen ist, die tatsächlichen Daten der Struktur. Wenn die Struktur einer neuen Variablen zugewiesen wird, wird sie kopiert. Die neue Variable und die ursprüngliche Variable enthalten daher zwei separate Kopien derselben Daten. Änderungen an einer Kopie wirken sich nicht auf die andere Kopie aus. Im Allgemeinen werden Klassen verwendet, um komplexeres Verhalten oder Daten zu modellieren, die nach dem Erstellen eines Klassenobjekts geändert werden sollen.
Klassen und Strukturen (C # -Programmierhandbuch)
quelle
MYTHOS 1: STRUKTE SIND LEICHTE KLASSEN
Dieser Mythos kommt in verschiedenen Formen vor. Einige Leute glauben, dass Werttypen keine Methoden oder anderes signifikantes Verhalten haben können oder sollten - sie sollten als einfache Datenübertragungstypen mit nur öffentlichen Feldern oder einfachen Eigenschaften verwendet werden. Der DateTime-Typ ist ein gutes Gegenbeispiel dazu: Es ist sinnvoll, dass er ein Werttyp ist, da er eine grundlegende Einheit wie eine Zahl oder ein Zeichen ist, und es ist auch sinnvoll, Berechnungen basierend auf durchführen zu können dessen Wert. Wenn man die Dinge aus der anderen Richtung betrachtet, sollten Datenübertragungstypen oft ohnehin Referenztypen sein - die Entscheidung sollte auf dem gewünschten Wert oder der Referenztypsemantik basieren, nicht auf der Einfachheit des Typs. Andere Leute glauben, dass Werttypen in Bezug auf die Leistung „leichter“ sind als Referenztypen. Die Wahrheit ist, dass Werttypen in einigen Fällen leistungsfähiger sind - sie erfordern keine Speicherbereinigung, es sei denn, sie sind in Kästchen verpackt, haben keinen Typidentifizierungsaufwand und erfordern beispielsweise keine Dereferenzierung. Auf andere Weise sind Referenztypen jedoch leistungsfähiger: Das Übergeben von Parametern, das Zuweisen von Werten zu Variablen, das Zurückgeben von Werten und ähnliche Vorgänge erfordern nur das Kopieren von 4 oder 8 Byte (je nachdem, ob Sie die 32-Bit- oder die 64-Bit-CLR ausführen ) anstatt alle Daten zu kopieren. Stellen Sie sich vor, ArrayList wäre irgendwie ein „reiner“ Wertetyp, und wenn Sie einen ArrayList-Ausdruck an eine Methode übergeben, müssen Sie alle Daten kopieren! In fast allen Fällen wird die Leistung ohnehin nicht wirklich durch diese Art von Entscheidung bestimmt. Engpässe treten fast nie dort auf, wo Sie glauben, und bevor Sie eine auf der Leistung basierende Entwurfsentscheidung treffen, Sie sollten 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 pro Instanz belegte Speicher ist nicht betroffen. (Es gibt Kosten in Bezug auf den Speicher, der für den Code selbst beansprucht wird, aber dieser fällt einmal und nicht für jede Instanz an.)
MYTHOS 2: REFERENZTYPEN LEBEN AUF DEM KOPF; Werttypen leben auf dem Stapel
Dieser wird oft durch Faulheit der Person verursacht, die ihn wiederholt. Der erste Teil ist korrekt - eine Instanz eines Referenztyps wird immer auf dem Heap erstellt. Es ist der zweite Teil, der Probleme verursacht. Wie bereits erwähnt, lebt der Wert einer Variablen überall dort, wo sie deklariert ist. Wenn Sie also eine Klasse mit einer Instanzvariablen vom Typ int haben, befindet sich der Wert dieser Variablen für ein bestimmtes Objekt immer dort, wo sich der Rest der Daten für das Objekt befindet. auf dem Haufen. Auf dem Stapel befinden sich nur lokale Variablen (innerhalb von Methoden deklarierte Variablen) und Methodenparameter. In C # 2 und höher leben sogar einige lokale Variablen nicht wirklich auf dem Stapel, wie Sie sehen werden, wenn wir uns anonyme Methoden in Kapitel 5 ansehen. SIND DIESE KONZEPTE JETZT RELEVANT? Wenn Sie verwalteten Code schreiben, sollten Sie sich zur Laufzeit Gedanken darüber machen, wie der Speicher am besten genutzt wird. Tatsächlich, Die Sprachspezifikation gibt keine Garantie dafür, was wo wohnt. Eine zukünftige Laufzeit kann möglicherweise einige Objekte auf dem Stapel erstellen, wenn sie weiß, dass sie damit durchkommen kann, oder der C # -Compiler kann Code generieren, der den Stapel kaum verwendet. Der nächste Mythos ist normalerweise nur ein Terminologieproblem.
MYTHOS 3: OBJEKTE WERDEN NACH STANDARD IN C # VERWEISEN
Dies ist wahrscheinlich der am weitesten verbreitete Mythos. Auch hier wissen die Personen, die diese Behauptung häufig (wenn auch nicht immer) aufstellen, wie sich C # tatsächlich verhält, aber sie wissen nicht, was „Referenzübergabe“ wirklich bedeutet. Leider ist dies verwirrend für Leute, die wissen, was es bedeutet. Die formale Definition der Referenzübergabe ist relativ kompliziert und umfasst l-Werte und eine ähnliche Informatik-Terminologie. Wichtig ist jedoch, dass die von Ihnen aufgerufene Methode den Wert der Variablen des Aufrufers ändern kann, wenn Sie eine Variable als Referenz übergeben durch Ändern des Parameterwerts. Denken Sie jetzt daran, dass der Wert einer Referenztypvariablen die Referenz und nicht das Objekt selbst ist. Sie können den Inhalt des Objekts ändern, auf das sich ein Parameter bezieht, ohne dass der Parameter selbst als Referenz übergeben wird. Zum Beispiel,
Wenn diese Methode aufgerufen wird, wird der Parameterwert (ein Verweis auf einen StringBuilder) als Wert übergeben. Wenn Sie den Wert der Builder-Variablen innerhalb der Methode ändern würden - beispielsweise mit der Anweisung builder = null; -, würde diese Änderung vom Aufrufer entgegen dem Mythos nicht gesehen. Es ist interessant festzustellen, dass nicht nur das "Referenz" -Bit des Mythos ungenau ist, sondern auch das "Objekte werden übergeben" -Bit. Objekte selbst werden weder als Referenz noch als Wert übergeben. Wenn es sich um einen Referenztyp handelt, wird entweder die Variable als Referenz übergeben oder der Wert des Arguments (die Referenz) wird als Wert übergeben. Abgesehen von allem anderen beantwortet dies die Frage, was passiert, wenn null als By-Value-Argument verwendet wird - wenn Objekte herumgereicht würden, würde dies zu Problemen führen, da kein Objekt übergeben werden würde! Stattdessen, Die Nullreferenz wird wie jede andere Referenz als Wert übergeben. Wenn diese kurze Erklärung Sie verwirrt hat, möchten Sie vielleicht meinen Artikel „Parameterübergabe in C #“ lesen (http://mng.bz/otVt ), was viel detaillierter geht. Diese Mythen sind nicht die einzigen. Boxen und Unboxen kommen für ihren fairen Anteil an Missverständnissen ins Spiel, die ich als nächstes zu klären versuchen werde.
Referenz: C # in Depth 3rd Edition von Jon Skeet
quelle
Ich denke, eine gute erste Annäherung ist "nie".
Ich denke, eine gute zweite Annäherung ist "nie".
Wenn Sie verzweifelt nach Perf sind, ziehen Sie sie in Betracht, aber messen Sie dann immer.
quelle
Ich zu tun hatte nur mit Windows Communication Foundation [WCF] Rohr Benannte und ich habe bemerkt , dass es keinen Sinn macht Structs zu verwenden , um , dass der Austausch von Daten , um sicherzustellen , Werttyp anstelle von Referenztyp .
quelle
Die C # -Struktur ist eine einfache Alternative zu einer Klasse. Es kann fast das Gleiche wie eine Klasse tun, aber es ist weniger "teuer", eine Struktur anstelle einer Klasse zu verwenden. Der Grund dafür ist ein bisschen technisch, aber um es zusammenzufassen, werden neue Instanzen einer Klasse auf dem Heap platziert, wo neu instanziierte Strukturen auf dem Stapel platziert werden. Außerdem haben Sie es nicht mit Verweisen auf Strukturen zu tun, wie mit Klassen, sondern Sie arbeiten direkt mit der Strukturinstanz. Dies bedeutet auch, dass wenn Sie eine Struktur an eine Funktion übergeben, diese nach Wert und nicht als Referenz erfolgt. Mehr dazu im Kapitel über Funktionsparameter.
Sie sollten also Strukturen verwenden, wenn Sie einfachere Datenstrukturen darstellen möchten, und insbesondere, wenn Sie wissen, dass Sie viele davon instanziieren werden. Es gibt viele Beispiele im .NET Framework, in denen Microsoft Strukturen anstelle von Klassen verwendet hat, z. B. die Struktur Point, Rectangle und Color.
quelle
Struct kann verwendet werden, um die Garbage Collection-Leistung zu verbessern. Während Sie sich normalerweise nicht um die GC-Leistung kümmern müssen, gibt es Szenarien, in denen dies ein Killer sein kann. Wie große Caches in Anwendungen mit geringer Latenz. In diesem Beitrag finden Sie ein Beispiel:
http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/
quelle
Struktur- oder Werttypen können in folgenden Szenarien verwendet werden:
Weitere Informationen zu den Werttypen und Werttypen finden Sie hier unter diesem Link
quelle
Verwenden Sie kurz struct, wenn:
1- Ihre Objekteigenschaften / Felder müssen nicht geändert werden. Ich meine, Sie möchten ihnen nur einen Anfangswert geben und sie dann lesen.
2- Eigenschaften und Felder in Ihrem Objekt sind vom Werttyp und nicht so groß.
In diesem Fall können Sie Strukturen für eine bessere Leistung und eine optimierte Speicherzuordnung nutzen, da sie nur Stapel anstelle von Stapeln und Heaps (in Klassen) verwenden.
quelle
Ich benutze selten eine Struktur für Dinge. Aber das bin nur ich. Es hängt davon ab, ob das Objekt nullwertfähig sein soll oder nicht.
Wie in anderen Antworten angegeben, verwende ich Klassen für reale Objekte. Ich habe auch die Einstellung, dass Strukturen zum Speichern kleiner Datenmengen verwendet werden.
quelle
Strukturen ähneln in den meisten Fällen Klassen / Objekten. Struktur kann Funktionen und Elemente enthalten und vererbt werden. Strukturen werden jedoch in C # nur zum Speichern von Daten verwendet . Strukturen benötigen weniger RAM als Klassen und sind für den Garbage Collector einfacher zu sammeln . Wenn Sie jedoch Funktionen in Ihrer Struktur verwenden, verwendet der Compiler diese Struktur tatsächlich sehr ähnlich als Klasse / Objekt. Wenn Sie also etwas mit Funktionen möchten , verwenden Sie Klasse / Objekt .
quelle