La source mentionnée par l'OP a une certaine crédibilité ... mais qu'en est-il de Microsoft - quelle est sa position sur l'utilisation des structures ? J'ai cherché un peu plus Apprendre de Microsoft et voici ce que j'ai trouvé :
Envisagez de définir une structure au lieu d'une classe si des instances de l'élément type sont petites et généralement de courte durée, ou si elles sont couramment intégrées dans d'autres objets.
Ne définissez pas une structure à moins que le type ne présente toutes les caractéristiques suivantes :
- Il représente logiquement une seule valeur, comme les types primitifs (entier, double, etc.).
- Il a une taille d'instance inférieure à 16 octets.
- Il est immuable.
- Elle ne devra pas être mise en boîte fréquemment.
Microsoft viole constamment ces règles
Ok, #2 et #3 en tout cas. Notre cher dictionnaire a 2 structs 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 - ce qui est tout à fait pardonnable puisque le numéro 4 ne serait probablement pas un problème. Si vous vous retrouvez à mettre en boîte une structure, repensez votre architecture.
Voyons pourquoi Microsoft utilise ces structs :
- Chaque structure,
Entry
y Enumerator
représentent des valeurs uniques.
- Vitesse
-
Entry
n'est jamais passé comme paramètre en dehors de la classe Dictionary. Un examen plus approfondi montre que pour satisfaire à l'implémentation de IEnumerable, Dictionary utilise la classe Enumerator
qu'il copie à chaque fois qu'un énumérateur est demandé ... c'est logique.
- Interne à la classe Dictionnaire.
Enumerator
est public car le Dictionary est énumérable et doit avoir une accessibilité égale à l'implémentation de l'interface IEnumerator - par exemple IEnumerator getter.
Mise à jour - De plus, il faut savoir que lorsqu'un struct implémente une interface - comme le fait Enumerator - et qu'il est coulé vers ce type implémenté, le struct devient un type de référence et est déplacé vers le tas. Interne à la classe Dictionary, Enumerator est 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 de l'obligation de garder les structures immuables ou de maintenir une taille d'instance de seulement 16 octets ou moins :
- Rien dans les structs ci-dessus n'est déclaré
readonly
- no immuable
- La taille de ces structures peut être bien supérieure à 16 octets.
-
Entry
a une durée de vie indéterminée (de Add()
à Remove()
, Clear()
ou la collecte des déchets) ;
Et... 4. Les deux structs stockent TKey et TValue, qui, nous le savons tous, sont tout à fait capables d'être des types de référence (information supplémentaire).
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é : 5ms
Temps total de remplissage : 889ms
Capacité : nombre d'éléments disponibles avant que le tableau interne ne doive être redimensionné.
MemSize Le dictionnaire est 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 qu'il faut pour redimensionner le tableau interne de 150 862 éléments à 31 2874 éléments. Quand on sait que chaque élément est copié séquentiellement via Array.CopyTo()
ce n'est pas trop mal.
Temps total de remplissage Il est vrai que les chiffres sont biaisés en raison de l'exploitation forestière et d'une OnResize
que j'ai ajouté à la source ; cependant, il est toujours impressionnant de remplir 300k entiers tout en redimensionnant 15 fois pendant l'opération. Juste par curiosité, quel serait le temps total de remplissage si je connaissais déjà la capacité ? 13ms
Donc, maintenant, et si Entry
étaient une classe ? Ces époques ou métriques seraient-elles vraiment si différentes ?
Capacité : 312874
MemSize : 2660827 octets
Redimensionnement terminé : 26ms
Temps total de remplissage : 964ms
La grande différence réside évidemment dans le redimensionnement. Une différence si le dictionnaire est initialisé avec la capacité ? Pas suffisamment pour s'en préoccuper... 12ms .
Ce qui se passe, c'est que 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 d'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 de Entry
comme type de référence peut être trouvé à l'adresse suivante MSDN : Conception de la structure . En bref :
Ne pas fournir un constructeur par défaut pour une structure.
Si une structure définit un constructeur par défaut, lorsque des tableaux du type structure, le moteur d'exécution du langage courant exécute automatiquement 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 de d'avoir des constructeurs par défaut.
C'est en fait assez simple et nous allons emprunter à Asimov Les trois lois de la robotique :
- La structure doit pouvoir être utilisée en toute sécurité
- La structure doit remplir sa fonction de manière efficace, à moins que cela ne soit contraire à la règle n° 1.
- La structure doit rester intacte pendant son utilisation, à moins que sa destruction ne soit nécessaire pour satisfaire à la règle n° 1.
... qu'est-ce qu'on en retient En bref, soyez responsable de l'utilisation des types de valeurs. Ils sont rapides et efficaces, mais ont la capacité de provoquer de nombreux comportements inattendus s'ils ne sont pas correctement gérés (c'est-à-dire des copies non intentionnelles).
0 votes
Je ne peux qu'être d'accord avec le premier point, les structs sont très utilisés dans la programmation des jeux par exemple.
305 votes
System.Drawing.Rectangle
viole ces trois règles.3 votes
Oui, en partie en tout cas. Je sais qu'il est utilisé pour des parties de jeux, comme NWN2 World Creator. Le C est toujours utilisé pour le noyau (moteur). XNA Game Studio, cherchez sur Google :)
7 votes
Il y a pas mal de jeux commerciaux écrits en C#, le fait est qu'ils sont utilisés pour du code optimisé
2 votes
Je les utilise parfois pour les "enums" à valeur de chaîne.
4 votes
Une autre situation où vous devriez utiliser les structs est lorsque vos objets n'ont pas besoin d'identité.
1 votes
J'ai toujours suivi le raisonnement de la deuxième modification de la question : si j'ai une poignée de types de valeurs liées, je vais créer une structure. Je peux avoir plusieurs de ces petites unités de données et je n'ai pas vraiment envie de créer un tas de fichiers de classe supplémentaires pour ce qui ne serait que quelques dizaines de lignes de code.
1 votes
Vous utilisez les structs lorsque vous ne voulez pas hériter de votre classe. C'est idéal si vous voulez créer des objets qui ne contiennent que des données.
36 votes
Les structures offrent de meilleures performances lorsque vous avez de petites collections de types de valeurs que vous souhaitez regrouper. Cela se produit tout le temps dans la programmation de jeux, par exemple, un sommet dans un modèle 3D aura une position, une coordonnée de texture et une normale, il sera aussi généralement immuable. Un modèle unique peut avoir quelques milliers de sommets, ou une douzaine, mais les structures fournissent moins de frais généraux dans ce scénario d'utilisation. J'ai vérifié cela à travers la conception de mon propre moteur.
0 votes
J'utiliserais les structs pour faire le lien entre le code non géré et les ensembles de données de type statique légers. Le compromis entre le gain de performance avec la gestion de la mémoire et le manque d'extensibilité dans la conception.
3 votes
C'est une très mauvaise raison de choisir la structure plutôt que la classe.
6 votes
@ErikForbes : Je pense c'est généralement considéré comme le plus gros "oops" de BCL.
0 votes
@ChrisW : Le plus grand défaut dans
Rectangle
est que ses membres sont des propriétés plutôt que des champs. L'utilisation de struct Le fait d'utiliser des propriétés alors que des champs auraient suffi est probablement responsable à 95 % de la notion de "mutable structs are evil" (il existe des utilisations légitimes des setters de propriétés sur des instances de struct en lecture seule ; à cause de cela, C# n'a commencé que récemment à interdire leur utilisation (en excluant les cas d'utilisation légitimes) ; les structs qui enveloppaient les membres dans des propriétés en lecture-écriture ont effectivement transformé du code qui aurait pu et dû générer des erreurs de compilation en un code qui compilerait mais ne fonctionnerait pas.0 votes
Duplication possible de Quand dois-je utiliser un struct au lieu d'une classe ?
0 votes
Une question intéressante pourrait être la suivante : quand dois-je utiliser des structures avec des méthodes, car chaque méthode représente 1 mot (16 octets) de plus en références donc, de toute évidence, cette structure sera assez lourde pour enfreindre la règle n° 2 et l'utilité des performances
2 votes
@ChrisW Doesnt
System.Drawing.Rectangle
représente une valeur unique ? Pouvez-vous nous expliquer cela ?0 votes
@MarsonMao Il représente une gauche, une droite, un haut, un bas, une hauteur et une largeur (c'est-à-dire 6 valeurs, certes liées entre elles).
8 votes
@ChrisW Je vois, mais ces valeurs ne représentent-elles pas un rectangle, c'est-à-dire une valeur "unique" ? Comme Vector3D ou Color, il y a aussi plusieurs valeurs à l'intérieur, mais je pense qu'elles représentent des valeurs uniques ?
2 votes
@MarsonMao L'une des caractéristiques d'une structure est qu'il s'agit d'un groupe de valeurs connexes à traiter comme une seule valeur. Rectangle répond certainement à cette caractéristique. Je pense que Chris essaie de faire la fine bouche avec la formulation de ce point.
2 votes
Les deux liens sont cassés.
1 votes
@weberc2 Chris dit que cette liste ne devrait pas être utilisée pour définir si l'on doit utiliser une structure. Le contre-exemple est
Rectangle
: quelque chose qui devrait très certainement être une structure, mais qui viole toutes ces conditions. Ce ne sont donc pas de bonnes conditions.0 votes
C'était une bonne question. J'ai lu beaucoup de choses sur les différences et j'ai trouvé la réponse ici, enfin.