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:
Ziehen Sie in Betracht, eine Struktur anstelle einer Klasse zu definieren, wenn Instanzen des Typs klein und häufig von kurzer Dauer sind oder häufig in andere Objekte eingebettet sind.
Definieren Sie keine Struktur, es sei denn, der Typ weist alle folgenden Merkmale auf:
- Es stellt logischerweise einen einzelnen Wert dar, ähnlich wie primitive Typen (Ganzzahl, Doppel usw.).
- Die Instanzgröße ist kleiner als 16 Byte.
- Es ist unveränderlich.
- Es muss nicht häufig verpackt werden.
Microsoft verstößt konsequent gegen diese Regeln
Okay, # 2 und # 3 sowieso. Unser geliebtes Wörterbuch hat 2 interne Strukturen:
[StructLayout(LayoutKind.Sequential)] // default for structs
private struct Entry //<Tkey, TValue>
{
// View code at *Reference Source
}
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator :
IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable,
IDictionaryEnumerator, IEnumerator
{
// View code at *Reference Source
}
* 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:
- Jede Struktur
Entry
und Enumerator
repräsentiert einzelne Werte.
- Geschwindigkeit
Entry
wird niemals als Parameter außerhalb der Dictionary-Klasse übergeben. Weitere Untersuchungen zeigen, dass Dictionary zur Erfüllung der Implementierung von IEnumerable die Enumerator
Struktur verwendet, die es jedes Mal kopiert, wenn ein Enumerator angefordert wird ... sinnvoll ist.
- Intern in der Dictionary-Klasse.
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 Referenztyp IEnumerator
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:
- Nichts in den obigen Strukturen wird deklariert
readonly
- nicht unveränderlich
- Die Größe dieser Struktur könnte weit über 16 Bytes liegen
Entry
hat eine unbestimmte Lebensdauer (von Add()
, auf Remove()
, 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: 312874
MemSize: 2660827 Bytes
Abgeschlossen Größe ändern: 5 ms Gesamtfüllzeit: 889
ms
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? 13ms
Was wäre Entry
nun eine Klasse? Würden sich diese Zeiten oder Metriken wirklich so stark unterscheiden?
Kapazität: 312874
Speichergröße: 2660827 Bytes
Abgeschlossen Größe ändern: 26 ms Gesamtfüllzeit: 964
ms
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. Um Entry
als Referenztyp zu verwenden, musste ich den folgenden Code einfügen:
/*
* Added to satisfy initialization of entry elements --
* this is where the extra time is spent resizing the Entry array
* **/
for (int i = 0 ; i < prime ; i++)
{
destinationArray[i] = new Entry( );
}
/* *********************************************** */
Der Grund, warum ich jedes Array-Element Entry
als Referenztyp initialisieren musste, finden Sie unter MSDN: Structure Design . Zusamenfassend:
Geben Sie keinen Standardkonstruktor für eine Struktur an.
Wenn eine Struktur einen Standardkonstruktor definiert, führt die Common Language Runtime beim Erstellen von Arrays der Struktur automatisch den Standardkonstruktor für jedes Arrayelement aus.
Einige Compiler, wie der C # -Compiler, erlauben Strukturen nicht, Standardkonstruktoren zu haben.
Es ist eigentlich ganz einfach und wir werden uns Asimovs Drei Gesetze der Robotik leihen :
- Die Struktur muss sicher zu verwenden sein
- Die Struktur muss ihre Funktion effizient ausführen, es sei denn, dies würde gegen Regel 1 verstoßen
- Die Struktur muss während ihrer Verwendung intakt bleiben, es sei denn, ihre Zerstörung ist erforderlich, um Regel 1 zu erfüllen
... 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).
System.Drawing.Rectangle
verstößt gegen alle drei Regeln.