Quand faut-il utiliser struct et pas class en C #? Mon modèle conceptuel est que les structures sont utilisées à des moments où l'élément n'est qu'une collection de types de valeur . Une façon de les logiquement tous tenir ensemble dans un ensemble cohérent.
Je suis tombé sur ces règles ici :
- Une structure doit représenter une seule valeur.
- Une structure doit avoir une empreinte mémoire inférieure à 16 octets.
- Une structure ne doit pas être modifiée après sa création.
Ces règles fonctionnent-elles? Que signifie une structure sémantiquement?
System.Drawing.Rectangle
viole ces trois règles.Réponses:
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é:
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:
* 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:
Entry
etEnumerator
représentent des valeurs uniques.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 laEnumerator
structure qu'il copie chaque fois qu'un énumérateur est demandé ... est logique.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érenceIEnumerator
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:
readonly
- non immuableEntry
a une durée de vie indéterminée (à partir deAdd()
, à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é : 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é? 13msAlors, maintenant, et si
Entry
c'était une classe? Ces temps ou mesures différeraient-ils vraiment autant?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'utiliserEntry
comme type de référence, j'ai dû insérer le code suivant: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:C'est en fait assez simple et nous emprunterons aux Trois lois de la robotique d'Asimov :
... 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).
la source
Decimal
ouDateTime
], alors si elle ne respecte pas les trois autres règles, elle doit être remplacée par une classe. Si une structure contient une collection fixe de variables, dont chacune peut contenir n'importe quelle valeur qui serait valide pour son type [par exempleRectangle
], alors elle doit respecter des règles différentes , dont certaines sont contraires à celles des structures "à valeur unique" .Dictionary
type d'entrée sur la base qu'il s'agit uniquement d'un type interne, les performances ont été considérées comme plus importantes que la sémantique ou une autre excuse. Mon point est qu'un type commeRectangle
devrait avoir son contenu exposé en tant que champs modifiables individuellement non pas "parce que" les avantages de performance l'emportent sur les imperfections sémantiques résultantes, mais parce que le type représente sémantiquement un ensemble fixe de valeurs indépendantes , et donc la structure mutable est à la fois plus performant et sémantiquement supérieur .Quand tu:
La mise en garde, cependant, est que les structures (arbitrairement grandes) sont plus coûteuses à transmettre que les références de classe (généralement un mot machine), donc les classes pourraient finir par être plus rapides dans la pratique.
la source
(Guid)null
(il est normal de convertir un null en type de référence), entre autres.Je ne suis pas d'accord avec les règles données dans le message d'origine. Voici mes règles:
1) Vous utilisez des structures pour les performances lorsqu'elles sont stockées dans des tableaux. (voir aussi Quand les structures sont-elles la réponse? )
2) Vous en avez besoin dans le code en passant des données structurées vers / depuis C / C ++
3) N'utilisez pas de structures sauf si vous en avez besoin:
la source
struct
de savoir comment il se comportera, mais si quelque chose eststruct
avec des champs exposés, c'est tout ce que l'on doit savoir. Si un objet expose une propriété d'un type de structure de champ exposé, et si le code lit cette structure dans une variable et la modifie, on peut prédire en toute sécurité qu'une telle action n'affectera pas l'objet dont la propriété a été lue à moins que ou jusqu'à ce que la structure soit écrite retour. En revanche, si la propriété était un type de classe mutable, la lire et la modifier pourrait mettre à jour l'objet sous-jacent comme prévu, mais ...Utilisez une structure lorsque vous voulez une sémantique de valeur par opposition à une sémantique de référence.
Éditer
Je ne sais pas pourquoi les gens votent en faveur de cela, mais c'est un point valable, et cela a été fait avant que l'op clarifie sa question, et c'est la raison fondamentale la plus fondamentale d'une structure.
Si vous avez besoin d'une sémantique de référence, vous avez besoin d'une classe et non d'une structure.
la source
En plus de la réponse "c'est une valeur", un scénario spécifique pour l'utilisation de structures est lorsque vous savez que vous avez un ensemble de données qui provoque des problèmes de récupération de place et que vous avez beaucoup d'objets. Par exemple, une grande liste / tableau d'instances Personne. La métaphore naturelle ici est une classe, mais si vous avez un grand nombre d'instances de personne à longue durée de vie, elles peuvent finir par obstruer GEN-2 et provoquer des blocages GC. Si le scénario le justifie, une approche potentielle ici consiste à utiliser un tableau (pas une liste) de structures Person , c'est-à-dire
Person[]
. Maintenant, au lieu d'avoir des millions d'objets dans GEN-2, vous avez un seul morceau sur la LOH (je suppose qu'il n'y a pas de chaînes, etc. ici - c'est-à-dire une valeur pure sans aucune référence). Cela a très peu d'impact sur le GC.Travailler avec ces données est gênant, car les données sont probablement trop grandes pour une structure, et vous ne voulez pas copier les valeurs de graisse tout le temps. Cependant, y accéder directement dans un tableau ne copie pas la structure - elle est en place (contrairement à un indexeur de liste, qui copie). Cela signifie beaucoup de travail avec les index:
Notez que garder les valeurs elles-mêmes immuables aidera ici. Pour une logique plus complexe, utilisez une méthode avec un paramètre by-ref:
Encore une fois, cela est en place - nous n'avons pas copié la valeur.
Dans des scénarios très spécifiques, cette tactique peut être très efficace; cependant, c'est un scernario assez avancé qui ne devrait être tenté que si vous savez ce que vous faites et pourquoi. La valeur par défaut ici serait une classe.
la source
List
Je crois, utilise unArray
arrière-plan. non ?De la spécification du langage C # :
Une alternative est de faire de Point une structure.
Désormais, un seul objet est instancié, celui du tableau, et les instances Point sont stockées en ligne dans le tableau.
Les constructeurs de structures sont invoqués avec le nouvel opérateur, mais cela n'implique pas que la mémoire est allouée. Au lieu d'allouer dynamiquement un objet et de lui renvoyer une référence, un constructeur de structure renvoie simplement la valeur de structure elle-même (généralement dans un emplacement temporaire sur la pile), et cette valeur est ensuite copiée si nécessaire.
Avec les classes, il est possible que deux variables référencent le même objet et donc possible que les opérations sur une variable affectent l'objet référencé par l'autre variable. Avec les structures, les variables ont chacune leur propre copie des données, et il n'est pas possible que les opérations sur l'une affectent l'autre. Par exemple, la sortie produite par le fragment de code suivant dépend si Point est une classe ou une structure.
Si Point est une classe, la sortie est 20 car a et b font référence au même objet. Si Point est une structure, la sortie est 10 car l'affectation de a à b crée une copie de la valeur, et cette copie n'est pas affectée par l'affectation suivante à ax
L'exemple précédent met en évidence deux des limitations des structures. Premièrement, la copie d'une structure entière est généralement moins efficace que la copie d'une référence d'objet, de sorte que l'attribution et le passage de paramètres de valeur peuvent être plus coûteux avec des structures qu'avec des types de référence. Deuxièmement, à l'exception des paramètres ref et out, il n'est pas possible de créer des références à des structures, ce qui exclut leur utilisation dans un certain nombre de situations.
la source
ref
à une structure mutable et savoir que toutes les mutations que la méthode externe effectuera sur elle seront effectuées avant son retour. C'est dommage .net n'a aucun concept de paramètres éphémères et de valeurs de retour de fonction, car ...ref
avec des objets de classe. Essentiellement, les variables locales, les paramètres et les valeurs de retour de fonction peuvent être persistants (par défaut), renvoyables ou éphémères. Il serait interdit au code de copier des choses éphémères sur tout ce qui survivrait à la portée actuelle. Les choses retournables seraient comme des choses éphémères, sauf qu'elles pourraient être renvoyées par une fonction. La valeur de retour d'une fonction serait liée par les restrictions les plus strictes applicables à l'un de ses paramètres "renvoyables".Les structures sont bonnes pour la représentation atomique des données, où lesdites données peuvent être copiées plusieurs fois par le code. Le clonage d'un objet est en général plus coûteux que la copie d'une structure, car cela implique d'allouer de la mémoire, d'exécuter le constructeur et de désallouer / garbage collection lorsque vous en avez terminé.
la source
Voici une règle de base.
Si tous les champs membres sont des types de valeur, créez une structure .
Si un champ membre est un type de référence, créez une classe . Cela est dû au fait que le champ de type de référence aura quand même besoin de l'allocation de segment de mémoire.
Exemples
la source
string
sont sémantiquement équivalents aux valeurs, et le stockage d'une référence à un objet immuable dans un champ n'entraîne pas d'allocation de segment de mémoire. La différence entre une structure avec des champs publics exposés et un objet de classe avec des champs publics exposés est que, étant donné la séquence de codevar q=p; p.X=4; q.X=5;
,p.X
aura la valeur 4 s'ila
s'agit d'un type de structure et 5 s'il s'agit d'un type de classe. Si l'on souhaite pouvoir modifier commodément les membres du type, il faut sélectionner «classe» ou «struct» selon que l'on souhaite ou nonq
affectentp
.ArraySegment<T>
encapsule unT[]
, qui est toujours un type de classe. Le type de structureKeyValuePair<TKey,TValue>
est souvent utilisé avec des types de classe comme paramètres génériques.Premièrement: scénarios d'interopérabilité ou lorsque vous devez spécifier la disposition de la mémoire
Deuxièmement: quand les données ont presque la même taille qu'un pointeur de référence de toute façon.
la source
Vous devez utiliser un "struct" dans les situations où vous souhaitez spécifier explicitement la disposition de la mémoire à l'aide de StructLayoutAttribute - généralement pour PInvoke.
Edit: Comment souligne que vous pouvez utiliser une classe ou une structure avec StructLayoutAttribute et c'est certainement vrai. En pratique, vous utiliseriez généralement une structure - elle est allouée sur la pile par rapport au tas, ce qui est logique si vous passez simplement un argument à un appel de méthode non managé.
la source
J'utilise des structures pour emballer ou déballer toute sorte de format de communication binaire. Cela inclut la lecture ou l'écriture sur disque, les listes de sommets DirectX, les protocoles réseau ou le traitement des données chiffrées / compressées.
Les trois lignes directrices que vous énumérez ne m'ont pas été utiles dans ce contexte. Quand j'ai besoin d'écrire quatre cents octets de choses dans un ordre particulier, je vais définir une structure de quatre cents octets, et je vais le remplir avec toutes les valeurs non liées qu'il est censé avoir, et je vais pour le configurer de la manière la plus logique à l'époque. (D'accord, quatre cents octets seraient assez étranges - mais à l'époque où j'écrivais des fichiers Excel pour gagner ma vie, je faisais face à des structures pouvant aller jusqu'à une quarantaine d'octets partout, car c'est la taille de certains enregistrements BIFF.)
la source
À l'exception des types de valeurs qui sont utilisés directement par le runtime et divers autres à des fins PInvoke, vous ne devez utiliser les types de valeurs que dans 2 scénarios.
la source
this
paramètre éphémère utilisé pour appeler ses méthodes); les classes permettent de dupliquer des références..NET prend en charge
value types
etreference types
(en Java, vous ne pouvez définir que des types de référence). Les instances dereference types
get sont allouées dans le tas managé et sont récupérées quand il n'y a aucune référence en suspens à elles. Les instances devalue types
, d'autre part, sont allouées dans lestack
, et donc la mémoire allouée est récupérée dès que leur portée se termine. Et bien sûr,value types
passez par la valeur, etreference types
par référence. Tous les types de données primitifs C #, à l'exception de System.String, sont des types de valeur.Quand utiliser struct sur classe,
En C #,
structs
sontvalue types
, les classes sontreference types
. Vous pouvez créer des types de valeur, en C #, à l'aide duenum
mot-clé et dustruct
mot - clé. L'utilisation d'unvalue type
au lieu d'unreference type
entraînera moins d'objets sur le tas géré, ce qui se traduira par une charge moindre sur le garbage collector (GC), des cycles GC moins fréquents et par conséquent de meilleures performances. Cependant, ilsvalue types
ont aussi leurs inconvénients. Faire le tour d'un grosstruct
est certainement plus coûteux que de passer une référence, c'est un problème évident. L'autre problème est la surcharge associée àboxing/unboxing
. Si vous vous demandez ceboxing/unboxing
que cela signifie, suivez ces liens pour une bonne explicationboxing
etunboxing
. En dehors des performances, il y a des moments où vous avez simplement besoin que les types aient une sémantique de valeur, ce qui serait très difficile (ou moche) à implémenter sireference types
êtes tout ce que vous avez. Vous ne devez utiliservalue types
que lorsque vous avez besoin de copier la sémantique ou que vous avez besoin d'une initialisation automatique, normalement dansarrays
ces types.la source
ref
. Passer une structure de taille parref
coûte le même prix que passer une référence de classe par valeur. Copier une structure de taille ou passer par une valeur est moins cher que d'effectuer une copie défensive d'un objet de classe et de stocker ou de transmettre une référence à cela. Les grandes classes de temps sont meilleures que les structures pour stocker les valeurs sont (1) lorsque les classes sont immuables (afin d'éviter la copie défensive), et chaque instance qui est créée passera beaucoup, ou ...readOnlyStruct.someMember = 5;
n'est pas de créersomeMember
une propriété en lecture seule, mais plutôt d'en faire un champ.Un struct est un type de valeur. Si vous affectez une structure à une nouvelle variable, la nouvelle variable contiendra une copie de l'original.
L'exécution des résultats suivants dans 5 instances de la structure stockée en mémoire:
Une classe est un type de référence. Lorsque vous affectez une classe à une nouvelle variable, la variable contient une référence à l'objet de classe d'origine.
L'exécution des résultats suivants dans une seule instance de l'objet classe en mémoire.
Les structures peuvent augmenter la probabilité d'une erreur de code. Si un objet valeur est traité comme un objet de référence mutable, un développeur peut être surpris lorsque des modifications apportées sont perdues de manière inattendue.
la source
J'ai fait un petit benchmark avec BenchmarkDotNet pour avoir une meilleure compréhension de l'avantage "struct" en nombre. Je teste en boucle un tableau (ou une liste) de structures (ou classes). La création de ces tableaux ou listes est hors de portée du benchmark - il est clair que la «classe» est plus lourde utilisera plus de mémoire et impliquera GC.
La conclusion est donc: soyez prudent avec LINQ et les structures cachées boxing / unboxing et l'utilisation de structures pour les microoptimisations reste strictement avec les tableaux.
PS Un autre point de référence sur le passage de struct / classe à travers la pile d'appels est là https://stackoverflow.com/a/47864451/506147
Code:
la source
Les types de structure en C # ou dans d'autres langages .net sont généralement utilisés pour contenir des éléments qui devraient se comporter comme des groupes de valeurs de taille fixe. Un aspect utile des types de structure est que les champs d'une instance de type structure peuvent être modifiés en modifiant l'emplacement de stockage dans lequel elle est conservée, et en aucune autre manière. Il est possible de coder une structure de telle manière que la seule façon de muter un champ est de construire une toute nouvelle instance, puis d'utiliser une affectation de structure pour muter tous les champs de la cible en les écrasant avec les valeurs de la nouvelle instance, mais à moins qu'un struct ne fournisse aucun moyen de créer une instance où ses champs ont des valeurs non par défaut, tous ses champs seront mutables si et si le struct lui-même est stocké dans un emplacement mutable.
Notez qu'il est possible de concevoir un type de structure afin qu'il se comporte essentiellement comme un type de classe, si la structure contient un champ de type classe privé et redirige ses propres membres vers celui de l'objet classe encapsulé. Par exemple, un
PersonCollection
pourrait offrir des propriétésSortedByName
etSortedById
, qui détiennent toutes deux une référence "immuable" à unPersonCollection
(défini dans leur constructeur) et l'implémenterGetEnumerator
en appelant soitcreator.GetNameSortedEnumerator
oucreator.GetIdSortedEnumerator
. De telles structures se comporteraient comme une référence à aPersonCollection
, sauf que leursGetEnumerator
méthodes seraient liées à différentes méthodes dans lePersonCollection
. On pourrait également avoir une structure envelopper une partie d'un tableau (par exemple, on pourrait définir uneArrayRange<T>
structure qui contiendrait unT[]
appeléArr
, un intOffset
et un intLength
, Avec une propriété indexée qui, pour un indexidx
dans la plage 0 àLength-1
, aurait accèsArr[idx+Offset]
). Malheureusement, s'ilfoo
s'agit d'une instance en lecture seule d'une telle structure, les versions actuelles du compilateur n'autoriseront pas les opérations comme,foo[3]+=4;
car elles n'ont aucun moyen de déterminer si ces opérations tenteront d'écrire dans les champs defoo
.Il est également possible de concevoir une structure pour se comporter comme un type de valeur similaire qui contient une collection de taille variable (qui semblera être copiée chaque fois que la structure est), mais la seule façon de faire fonctionner cela est de s'assurer qu'aucun objet auquel le struct détient une référence sera jamais exposée à tout ce qui pourrait la muter. Par exemple, on pourrait avoir une structure de type tableau qui contient un tableau privé, et dont la méthode "put" indexée crée un nouveau tableau dont le contenu est similaire à celui de l'original à l'exception d'un élément modifié. Malheureusement, il peut être quelque peu difficile de faire fonctionner efficacement ces structures. Bien qu'il y ait des moments où la sémantique des structures peut être pratique (par exemple, pouvoir passer une collection de type tableau à une routine, l'appelant et l'appelé sachant tous les deux que le code extérieur ne modifiera pas la collection,
la source
Non - je ne suis pas entièrement d'accord avec les règles. Ce sont de bonnes lignes directrices à considérer avec les performances et la normalisation, mais pas à la lumière des possibilités.
Comme vous pouvez le voir dans les réponses, il existe de nombreuses façons créatives de les utiliser. Donc, ces directives doivent être simplement cela, toujours pour des raisons de performances et d'efficacité.
Dans ce cas, j'utilise des classes pour représenter des objets du monde réel dans leur plus grande forme, j'utilise des structures pour représenter des objets plus petits qui ont des utilisations plus exactes. Comme vous l'avez dit, "un tout plus cohérent". Le mot clé étant cohérent. Les classes seront davantage des éléments orientés objet, tandis que les structures peuvent avoir certaines de ces caractéristiques, bien qu'à une plus petite échelle. OMI.
Je les utilise beaucoup dans les balises Treeview et Listview où les attributs statiques communs sont accessibles très rapidement. J'ai toujours eu du mal à obtenir ces informations d'une autre manière. Par exemple, dans mes applications de base de données, j'utilise un Treeview où j'ai des tables, des SP, des fonctions ou tout autre objet. Je crée et remplis ma structure, la mets dans la balise, la retire, récupère les données de la sélection et ainsi de suite. Je ne ferais pas ça avec un cours!
J'essaie de les garder petits, de les utiliser dans des situations à instance unique et de les empêcher de changer. Il est prudent de connaître la mémoire, l'allocation et les performances. Et les tests sont tellement nécessaires.
la source
double
valeurs pour ces coordonnées, une telle spécification l'obligerait à se comporter sémantiquement de façon identique à une structure de champ exposé à l'exception de certains détails du comportement multithread (la classe immuable serait meilleure dans certains cas, tandis que la structure de champ exposé serait meilleure dans d'autres; une structure dite "immuable" serait être pire dans tous les cas).Ma règle est
1, utilisez toujours la classe;
2, S'il y a un problème de performance, j'essaie de changer une classe en struct en fonction des règles mentionnées par @IAbstract, puis je fais un test pour voir si ces changements peuvent améliorer les performances.
la source
Foo
encapsule une collection fixe de valeurs indépendantes (par exemple les coordonnées d'un point) que l'on voudra parfois faire circuler en groupe et parfois changer indépendamment. Je n'ai trouvé aucun modèle d'utilisation de classes qui combine les deux objectifs presque aussi bien qu'une simple structure de champ exposé (qui, étant une collection fixe de variables indépendantes, convient parfaitement).public readonly
champs dans mes types, car la création de propriétés en lecture seule est tout simplement trop de travail pour pratiquement aucun avantage.)MyListOfPoint[3].Offset(2,3);
envar temp=MyListOfPoint[3]; temp.Offset(2,3);
une transformation qui est fausse lorsqu'elle est appliquée ...Offset
méthode. La bonne façon d'empêcher un tel code faux ne devrait pas être de rendre les structures inutilement immuables, mais plutôt de permettre à des méthodes commeOffset
d'être étiquetées avec un attribut interdisant la transformation susmentionnée. Les conversions numériques implicites auraient également pu être bien meilleures si elles pouvaient être étiquetées de manière à ne s'appliquer que dans les cas où leur invocation serait évidente. Si des surcharges existent pourfoo(float,float)
etfoo(double,double)
, je dirais qu'essayer d'utiliser afloat
et adouble
ne devrait pas souvent appliquer une conversion implicite, mais devrait plutôt être une erreur.double
valeur à afloat
, ou la passer à une méthode qui peut prendre unfloat
argument mais pasdouble
, ferait presque toujours ce que le programmeur voulait. En revanche, attribuer unefloat
expression àdouble
sans transtypage explicite est souvent une erreur. Le seul moment où ladouble->float
conversion implicite entraînerait des problèmes serait le moment où elle entraînerait la sélection d'une surcharge non idéale. Je suppose que la bonne façon d'empêcher cela n'aurait pas dû être d'interdire impliciter double-> float, mais de baliser les surcharges avec des attributs pour interdire la conversion.Une classe est un type de référence. Lorsqu'un objet de la classe est créé, la variable à laquelle l'objet est affecté ne contient qu'une référence à cette mémoire. Lorsque la référence d'objet est affectée à une nouvelle variable, la nouvelle variable fait référence à l'objet d'origine. Les modifications apportées via une variable sont reflétées dans l'autre variable car elles font toutes deux référence aux mêmes données. Un struct est un type de valeur. Lorsqu'une structure est créée, la variable à laquelle la structure est affectée contient les données réelles de la structure. Lorsque la structure est affectée à une nouvelle variable, elle est copiée. La nouvelle variable et la variable d'origine contiennent donc deux copies distinctes des mêmes données. Les modifications apportées à une copie n'affectent pas l'autre copie. En général, les classes sont utilisées pour modéliser un comportement plus complexe ou des données destinées à être modifiées après la création d'un objet de classe.
Classes et structures (Guide de programmation C #)
la source
MYTHE N ° 1: LES STRUCTS SONT DES CLASSES LÉGÈRES
Ce mythe se présente sous diverses formes. Certaines personnes croient que les types de valeur ne peuvent pas ou ne doivent pas avoir de méthodes ou d'autres comportements importants - ils doivent être utilisés comme de simples types de transfert de données, avec uniquement des champs publics ou des propriétés simples. Le type DateTime est un bon contre-exemple à cela: il est logique qu'il soit un type de valeur, en termes d'être une unité fondamentale comme un nombre ou un caractère, et il est également logique qu'il puisse effectuer des calculs basés sur Sa valeur. En regardant les choses dans l'autre sens, les types de transfert de données doivent souvent être des types de référence de toute façon - la décision doit être basée sur la valeur souhaitée ou la sémantique du type de référence, pas sur la simplicité du type. D'autres personnes pensent que les types de valeur sont «plus légers» que les types de référence en termes de performances. La vérité est que, dans certains cas, les types de valeur sont plus performants: ils ne nécessitent pas de récupération de place sauf s'ils sont encadrés, n'ont pas la surcharge d'identification de type et ne nécessitent pas de déréférencement, par exemple. Mais à d'autres égards, les types de référence sont plus performants: le passage de paramètres, l'attribution de valeurs à des variables, le retour de valeurs et des opérations similaires ne nécessitent que 4 ou 8 octets pour être copiés (selon que vous exécutez le CLR 32 bits ou 64 bits). ) plutôt que de copier toutes les données. Imaginez si ArrayList était en quelque sorte un type de valeur «pur», et passer une expression ArrayList à une méthode impliquait de copier toutes ses données! Dans presque tous les cas, les performances ne sont pas vraiment déterminées par ce type de décision de toute façon. Les goulots d'étranglement ne sont presque jamais là où vous pensez qu'ils seront, et avant de prendre une décision de conception basée sur les performances, vous devez mesurer les différentes options. Il convient de noter que la combinaison des deux croyances ne fonctionne pas non plus. Peu importe le nombre de méthodes d'un type (que ce soit une classe ou une structure), la mémoire utilisée par instance n'est pas affectée. (Il y a un coût en termes de mémoire occupée pour le code lui-même, mais il est engagé une fois plutôt que pour chaque instance.)
MYTHE # 2: LES TYPES DE RÉFÉRENCE VIVENT SUR LE CŒUR; TYPES DE VALEURS EN DIRECT SUR LA PILE
Celui-ci est souvent causé par la paresse de la personne qui le répète. La première partie est correcte: une instance d'un type de référence est toujours créée sur le tas. C'est la deuxième partie qui pose problème. Comme je l'ai déjà noté, la valeur d'une variable vit partout où elle est déclarée, donc si vous avez une classe avec une variable d'instance de type int, la valeur de cette variable pour un objet donné sera toujours là où le reste des données de l'objet est ... sur le tas. Seules les variables locales (variables déclarées dans les méthodes) et les paramètres de méthode vivent sur la pile. En C # 2 et versions ultérieures, même certaines variables locales ne vivent pas vraiment sur la pile, comme vous le verrez lorsque nous examinons les méthodes anonymes au chapitre 5. CES CONCEPTS SONT-ILS PERTINENTS MAINTENANT? On peut soutenir que si vous écrivez du code managé, vous devez laisser le runtime s'inquiéter de la meilleure façon d'utiliser la mémoire. En effet, la spécification de la langue ne garantit pas ce qui vit où; un futur runtime pourra peut-être créer des objets sur la pile s'il sait qu'il peut s'en tirer, ou le compilateur C # pourrait générer du code qui utilise à peine la pile. Le mythe suivant n'est généralement qu'un problème de terminologie.
MYTHE # 3: LES OBJETS SONT PASSÉS PAR RÉFÉRENCE EN C # PAR DÉFAUT
C'est probablement le mythe le plus répandu. Encore une fois, les personnes qui font cette affirmation savent souvent (mais pas toujours) comment se comporte réellement C #, mais elles ne savent pas ce que signifie «passer par référence». Malheureusement, cela prête à confusion pour les personnes qui savent ce que cela signifie. La définition formelle du passage par référence est relativement compliquée, impliquant des valeurs l et une terminologie informatique similaire, mais l'important est que si vous passez une variable par référence, la méthode que vous appelez peut changer la valeur de la variable de l'appelant en modifiant sa valeur de paramètre. Maintenant, rappelez-vous que la valeur d'une variable de type référence est la référence, pas l'objet lui-même. Vous pouvez modifier le contenu de l'objet auquel un paramètre fait référence sans que le paramètre lui-même soit transmis par référence. Par exemple,
Lorsque cette méthode est appelée, la valeur du paramètre (une référence à un StringBuilder) est passée par valeur. Si vous deviez changer la valeur de la variable de générateur dans la méthode - par exemple, avec l'instruction builder = null; - ce changement ne serait pas vu par l'appelant, contrairement au mythe. Il est intéressant de noter que non seulement le bit «par référence» du mythe est inexact, mais aussi le bit «les objets sont passés». Les objets eux-mêmes ne sont jamais transmis, ni par référence ni par valeur. Lorsqu'un type de référence est impliqué, la variable est transmise par référence ou la valeur de l'argument (la référence) est transmise par valeur. En plus de toute autre chose, cela répond à la question de savoir ce qui se passe lorsque null est utilisé comme argument par valeur - si des objets étaient transmis, cela causerait des problèmes, car il n'y aurait pas d'objet à passer! Au lieu, la référence nulle est passée par valeur de la même manière que toute autre référence. Si cette explication rapide vous a laissé perplexe, vous voudrez peut-être consulter mon article, «Passage de paramètres en C #» (http://mng.bz/otVt ), qui va beaucoup plus en détail. Ces mythes ne sont pas les seuls à exister. La boxe et le déballage arrivent pour leur juste part de malentendus, que j'essaierai de clarifier ensuite.
Référence: C # in Depth 3rd Edition par Jon Skeet
la source
Je pense qu'une bonne première approximation est "jamais".
Je pense qu'une bonne seconde approximation est "jamais".
Si vous avez désespérément besoin de perf, considérez-les, mais mesurez toujours.
la source
Je venais de traiter avec le canal nommé Windows Communication Foundation [WCF] et j'ai remarqué qu'il est logique d'utiliser Structs afin de garantir que l'échange de données est de type valeur au lieu de type référence .
la source
La structure C # est une alternative légère à une classe. Il peut faire presque la même chose qu'une classe, mais il est moins «coûteux» d'utiliser une structure plutôt qu'une classe. La raison en est un peu technique, mais pour résumer, de nouvelles instances d'une classe sont placées sur le tas, où les structures nouvellement instanciées sont placées sur la pile. De plus, vous ne traitez pas avec des références à des structures, comme avec des classes, mais à la place vous travaillez directement avec l'instance de structure. Cela signifie également que lorsque vous passez une structure à une fonction, c'est par valeur, plutôt que comme référence. Il y a plus à ce sujet dans le chapitre sur les paramètres de fonction.
Donc, vous devez utiliser des structures lorsque vous souhaitez représenter des structures de données plus simples, et surtout si vous savez que vous en instancierez beaucoup. Il existe de nombreux exemples dans le framework .NET, où Microsoft a utilisé des structures au lieu de classes, par exemple la structure Point, Rectangle et Color.
la source
Struct peut être utilisé pour améliorer les performances de la récupération de place. Bien que vous n'ayez généralement pas à vous soucier des performances du GC, il existe des scénarios où cela peut être un tueur. Comme les grands caches dans les applications à faible latence. Voir cet article pour un exemple:
http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/
la source
Les types de structure ou de valeur peuvent être utilisés dans les scénarios suivants -
Vous pouvez en savoir plus sur les types de valeurs et les types de valeurs ici sur ce lien
la source
En bref, utilisez struct si:
1- Les propriétés / champs de votre objet n'ont pas besoin d'être modifiés. Je veux dire que vous voulez simplement leur donner une valeur initiale, puis les lire.
2- Les propriétés et les champs de votre objet sont de type valeur et ils ne sont pas si grands.
Si c'est le cas, vous pouvez profiter des structures pour de meilleures performances et une allocation de mémoire optimisée car elles n'utilisent que des piles plutôt que des piles et des tas (en classes)
la source
J'utilise rarement une structure pour les choses. Mais c'est juste moi. Cela dépend si j'ai besoin que l'objet soit nul ou non.
Comme indiqué dans d'autres réponses, j'utilise des classes pour des objets du monde réel. J'ai également l'état d'esprit des structures utilisées pour stocker de petites quantités de données.
la source
Les structures sont dans la plupart des cas comme des classes / objets. La structure peut contenir des fonctions, des membres et peut être héritée. Mais les structures sont en C # utilisées uniquement pour la conservation des données . Les structures prennent moins de RAM que les classes et sont plus faciles à collecter pour le garbage collector . Mais lorsque vous utilisez des fonctions dans votre structure, le compilateur prend en fait cette structure de la même manière que classe / objet, donc si vous voulez quelque chose avec des fonctions, alors utilisez classe / objet .
la source