La source référencée par l'OP a une certaine crédibilité ... mais qu'en est-il de Microsoft - quelle est la position sur l'utilisation des structures? J'ai cherché un apprentissage supplémentaire auprès de Microsoft , et voici ce que j'ai trouvé:
Envisagez de définir une structure au lieu d'une classe si les instances du type sont petites et généralement de courte durée ou sont généralement intégrées dans d'autres objets.
Ne définissez une structure que si le type possède toutes les caractéristiques suivantes:
- Il représente logiquement une valeur unique, similaire aux types primitifs (entier, double, etc.).
- Il a une taille d'instance inférieure à 16 octets.
- C'est immuable.
- Il n'aura pas à être emballé fréquemment.
Microsoft viole systématiquement ces règles
D'accord, n ° 2 et n ° 3 de toute façon. Notre dictionnaire bien-aimé a 2 structures internes:
[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
}
* Source de référence
La source 'JonnyCantCode.com' a obtenu 3 sur 4 - tout à fait pardonnable car # 4 ne serait probablement pas un problème. Si vous vous retrouvez en train de boxer une structure, repensez votre architecture.
Voyons pourquoi Microsoft utiliserait ces structures:
- Chaque structure
Entry
et Enumerator
représentent des valeurs uniques.
- La vitesse
Entry
n'est jamais passé en tant que paramètre en dehors de la classe Dictionary. Une enquête plus approfondie montre que pour satisfaire l'implémentation de IEnumerable, Dictionary utilise la Enumerator
structure qu'il copie chaque fois qu'un énumérateur est demandé ... est logique.
- Interne à la classe Dictionary.
Enumerator
est public car Dictionary est énumérable et doit avoir une accessibilité égale à l'implémentation de l'interface IEnumerator - par exemple le getter IEnumerator.
Mise à jour - En outre, sachez que lorsqu'une structure implémente une interface - comme le fait l'énumérateur - et est convertie en ce type implémenté, la structure devient un type de référence et est déplacée vers le segment de mémoire. En interne à la classe Dictionary, Enumerator est toujours un type de valeur. Cependant, dès qu'une méthode appelle GetEnumerator()
, un type de référence IEnumerator
est renvoyé.
Ce que nous ne voyons pas ici, c'est une tentative ou une preuve d'exigence de garder les structures immuables ou de maintenir une taille d'instance de seulement 16 octets ou moins:
- Rien dans les structures ci-dessus n'est déclaré
readonly
- non immuable
- La taille de ces structures pourrait être bien supérieure à 16 octets
Entry
a une durée de vie indéterminée (à partir de Add()
, à Remove()
, Clear()
ou la collecte des ordures);
Et ... 4. Les deux structures stockent TKey et TValue, qui, nous le savons tous, sont tout à fait capables d'être des types de référence (informations bonus ajoutées)
Malgré les clés hachées, les dictionnaires sont rapides en partie parce que l'instanciation d'une structure est plus rapide qu'un type de référence. Ici, j'ai un Dictionary<int, int>
qui stocke 300 000 entiers aléatoires avec des clés incrémentées séquentiellement.
Capacité: 312874
MemSize: 2660827 octets
Redimensionnement terminé: 5 ms
Temps total de remplissage: 889 ms
Capacité : nombre d'éléments disponibles avant le redimensionnement du tableau interne.
MemSize : déterminé en sérialisant le dictionnaire dans un MemoryStream et en obtenant une longueur d'octet (suffisamment précise pour nos besoins).
Redimensionnement terminé : le temps nécessaire pour redimensionner la matrice interne de 150862 éléments à 312874 éléments. Lorsque vous pensez que chaque élément est copié séquentiellement via Array.CopyTo()
, ce n'est pas trop minable.
Temps total à remplir : certes biaisé en raison de la journalisation et d'un OnResize
événement que j'ai ajouté à la source; cependant, toujours impressionnant pour remplir 300k entiers tout en redimensionnant 15 fois pendant l'opération. Par simple curiosité, quel serait le temps total à remplir si je connaissais déjà la capacité? 13ms
Alors, maintenant, et si Entry
c'était une classe? Ces temps ou mesures différeraient-ils vraiment autant?
Capacité: 312874
MemSize: 2660827 octets
Redimensionnement terminé: 26ms
Temps total de remplissage: 964ms
De toute évidence, la grande différence réside dans le redimensionnement. Une différence si le dictionnaire est initialisé avec la capacité? Pas assez pour se préoccuper de ... 12ms .
Ce qui se passe est, car Entry
est une structure, elle ne nécessite pas d'initialisation comme un type de référence. C'est à la fois la beauté et le fléau du type valeur. Afin de l'utiliser Entry
comme type de référence, j'ai dû insérer le code suivant:
/*
* 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( );
}
/* *********************************************** */
La raison pour laquelle j'ai dû initialiser chaque élément de tableau en Entry
tant que type de référence se trouve sur MSDN: Structure Design . En bref:
Ne fournissez pas de constructeur par défaut pour une structure.
Si une structure définit un constructeur par défaut, lorsque des tableaux de la structure sont créés, le Common Language Runtime exécute automatiquement le constructeur par défaut sur chaque élément du tableau.
Certains compilateurs, tels que le compilateur C #, ne permettent pas aux structures d'avoir des constructeurs par défaut.
C'est en fait assez simple et nous emprunterons aux Trois lois de la robotique d'Asimov :
- La structure doit être sûre à utiliser
- La structure doit remplir sa fonction efficacement, sauf si cela enfreindrait la règle # 1
- La structure doit rester intacte pendant son utilisation, sauf si sa destruction est requise pour satisfaire à la règle n ° 1.
... que retenons-nous de cela : bref, soyez responsable de l'utilisation des types de valeur. Ils sont rapides et efficaces, mais ont la capacité de provoquer de nombreux comportements inattendus s'ils ne sont pas correctement entretenus (c'est-à-dire des copies involontaires).
System.Drawing.Rectangle
viole ces trois règles.