Quand utilisez-vous une structure au lieu d'une classe? [fermé]

174

Quelles sont vos règles empiriques pour savoir quand utiliser les structures par rapport aux classes? Je pense à la définition C # de ces termes, mais si votre langage utilise des concepts similaires, j'aimerais également connaître votre opinion.

J'ai tendance à utiliser des classes pour presque tout, et à n'utiliser des structures que lorsque quelque chose est très simpliste et devrait être un type de valeur, tel qu'un numéro de téléphone ou quelque chose comme ça. Mais cela semble être une utilisation relativement mineure et j'espère qu'il y a des cas d'utilisation plus intéressants.

RationalGeek
la source
4
Doublon possible: stackoverflow.com/questions/6267957/struct-vs-class
FrustratedWithFormsDesigner
7
@Frustrated Impossible de marquer une question en tant que duplicata de quelque chose sur un autre site, bien que ce soit certainement lié.
Adam Lear
@ Anna Lear: Je sais, j'ai voté pour la migration et non pour la duplication. Une fois migré, il peut être fermé correctement en double .
FrustratedWithFormsDesigner
5
@ Frustré Je ne pense pas qu'il soit nécessaire de migrer.
Adam Lear
15
Cela semble être une question beaucoup plus adaptée à P.SE vs SO. C'est pourquoi je l'ai demandé ici.
RationalGeek

Réponses:

169

La règle générale à suivre est que les structures doivent être de petites collections simples (à un niveau) de propriétés connexes, immuables une fois créées; pour toute autre chose, utilisez une classe.

C # est bien en ce que les structures et les classes n'ont pas de différences explicites dans la déclaration autres que le mot-clé de définition; ainsi, si vous pensez que vous devez "mettre à niveau" une structure vers une classe, ou inversement, "rétrograder" une classe sur une structure, il suffit généralement de changer le mot clé (il existe quelques autres pièges; les structures ne peuvent pas dériver de toute autre classe ou type de structure, et ils ne peuvent pas définir explicitement un constructeur par défaut sans paramètre).

Je dis "principalement", car le plus important à savoir sur les struct est que, comme il s'agit de types de valeur, les traiter comme des classes (types de référence) peut s'avérer pénible. En particulier, rendre les propriétés d'une structure mutables peut entraîner un comportement inattendu.

Par exemple, supposons que vous ayez une classe SimpleClass avec deux propriétés, A et B. Vous instanciez une copie de cette classe, initialisez A et B, puis transmettez l'instance à une autre méthode. Cette méthode modifie davantage A et B. De retour dans la fonction appelante (celle qui a créé l'instance), A et B de votre instance auront les valeurs que leur donne la méthode appelée.

Maintenant, vous en faites une structure. Les propriétés sont toujours modifiables. Vous effectuez les mêmes opérations avec la même syntaxe qu'auparavant, mais maintenant, les nouvelles valeurs de A et B ne sont plus dans l'instance après l'appel de la méthode. Qu'est-il arrivé? Eh bien, votre classe est maintenant une structure, ce qui signifie que c'est un type valeur. Si vous transmettez un type de valeur à une méthode, la valeur par défaut (sans mot-clé out ou ref) est de passer "par valeur"; une copie superficielle de l'instance est créée pour être utilisée par la méthode, puis détruite lorsque la méthode est terminée en laissant l'instance initiale intacte.

Cela devient encore plus déroutant si vous aviez un type de référence en tant que membre de votre structure (non pas interdit, mais extrêmement mauvais dans pratiquement tous les cas); la classe ne serait pas clonée (uniquement la référence de la structure à celle-ci), ainsi les modifications apportées à la structure n'affecteront pas l'objet d'origine, mais les modifications apportées à la sous-classe de la structure affecteront l'instance à partir du code appelant. Cela peut très facilement placer des structures mutables dans des états très incohérents pouvant causer des erreurs très loin du problème réel.

Pour cette raison, pratiquement toutes les autorités de C # ont pour principe de toujours rendre vos structures immuables; permet au consommateur de spécifier les valeurs des propriétés uniquement lors de la construction d'un objet et ne fournit jamais aucun moyen de modifier les valeurs de cette instance. Les champs en lecture seule, ou les propriétés get-only, sont la règle. Si le consommateur souhaite modifier la valeur, il peut créer un nouvel objet en fonction des valeurs de l'ancien, avec les modifications souhaitées, ou appeler une méthode qui en fera de même. Cela les oblige à traiter une seule instance de votre structure comme une "valeur" conceptuelle, indivisible et distincte de toutes les autres (mais éventuellement équivalente). S'ils effectuent une opération sur une "valeur" stockée par votre type, ils obtiendront une nouvelle "valeur" différente de leur valeur initiale.

Pour un bon exemple, regardez le type DateTime. Vous ne pouvez affecter aucun des champs d'une instance DateTime directement; vous devez soit en créer une nouvelle, soit appeler une méthode sur la méthode existante qui produira une nouvelle instance. En effet, une date et une heure sont une "valeur", comme le nombre 5, et une modification du nombre 5 entraîne une nouvelle valeur qui n'est pas 5. Juste parce que 5 + 1 = 6 ne veut pas dire que 5 est maintenant 6 parce que vous y avez ajouté 1. DateTimes fonctionnent de la même manière; 12:00 ne "devient" pas 12:01 si vous ajoutez une minute, vous obtenez une nouvelle valeur 12:01 qui est différente de 12:00. S'il s'agit d'un état logique pour votre type (de bons exemples conceptuels qui ne sont pas intégrés à .NET sont Money, Distance, Weight, etc., où les opérations doivent prendre en compte toutes les parties de la valeur), utilisez ensuite une structure et concevez-la en conséquence. Dans la plupart des autres cas où les sous-éléments d'un objet doivent être modifiables indépendamment, utilisez une classe.

KeithS
la source
1
Bonne réponse! Je dirais que l'on pourrait lire la méthodologie et le livre de «Domain Driven Development» (développement de domaine), ce qui semble un peu lié à votre opinion sur la raison pour laquelle vous devriez utiliser des classes ou des structures en fonction du concept que vous essayez réellement d'implémenter
Maxim Popravko
1
Juste un petit détail qui mérite d'être mentionné: vous ne pouvez pas définir votre propre constructeur par défaut sur une structure. Vous devez vous débrouiller avec celui qui initialise tout à sa valeur par défaut. D'habitude, c'est assez mineur, en particulier pour les classes qui sont un bon candidat pour être un struct. Mais cela signifiera que changer une classe en struct peut impliquer plus que changer un mot-clé.
Laurent Bourgault-Roy
1
@ LaurentBourgault-Roy - C'est vrai. Il y a aussi d'autres pièges; Les structures ne peuvent pas avoir de hiérarchie d'héritage, par exemple. Ils peuvent implémenter des interfaces, mais ne peuvent dériver d’autre que System.ValueType (implicitement en tant que structs).
KeithS
En tant que développeur JavaScript, je dois demander deux choses. En quoi les littéraux d'objet diffèrent-ils des utilisations typiques des structures et pourquoi est-il important que les structures soient immuables?
Erik Reppen
1
@ErikReppen: En réponse à vos deux questions: lorsqu'une structure est transmise à une fonction, elle est transmise par valeur. Avec une classe, vous transmettez une référence à la classe (toujours par valeur). Eric Lippert explique pourquoi ceci est un problème: blogs.msdn.com/b/ericlippert/archive/2008/05/14/… . Le dernier paragraphe de KeithS couvre également ces questions.
Brian
14

La réponse: "Utiliser une structure pour des constructions de données pures et une classe pour des objets avec des opérations" est définitivement un mauvais IMO. Si une structure contient un grand nombre de propriétés, alors une classe est presque toujours plus appropriée. Microsoft dit souvent, du point de vue de l'efficacité, que si votre type est supérieur à 16 octets, il devrait s'agir d'une classe.

https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes

bytedev
la source
1
Absolument. Je voterais si je pouvais. Je ne comprends pas d'où vient l'idée que les structures sont des données et que les objets ne le sont pas. Une structure en C # n'est qu'un mécanisme d'allocation sur la pile. Des copies de l'ensemble de la structure sont transmises au lieu d'une simple copie d'un pointeur d'objet.
mike30
2
@mike - Les structures C # ne sont pas nécessairement allouées sur la pile.
Lee
1
@mike L'idée "les structures sont faites pour les données" vient probablement de l'habitude acquise par de nombreuses années de programmation en C. C n'a pas de classes et ses structures sont des conteneurs de données uniquement.
Konamiman
De toute évidence, il y a au moins 3 programmeurs C (qui ont obtenu le vote) qui ont lu la réponse de Michael K. ;-)
bytedev
10

La différence la plus importante entre a classet a structest ce qui se passe dans la situation suivante:

  Chose 1 = nouvelle chose ();
  thing1.somePropertyOrField = 5;
  Chose chose2 = chose1;
  thing2.somePropertyOrField = 9;

Quel devrait être l'effet de la dernière déclaration thing1.somePropertyOrField? Si Thingune structure, et somePropertyOrFieldest un champ public exposé, les objets thing1et thing2seront "détachés" les uns des autres, de sorte que la dernière déclaration n'affectera pas thing1. Si Thingest une classe, alors thing1et thing2seront attachés les uns aux autres, et cette dernière déclaration écrira thing1.somePropertyOrField. On devrait utiliser une structure dans les cas où la sémantique précédente aurait plus de sens et utiliser une classe dans les cas où la sémantique précédente aurait plus de sens.

Notez que si certaines personnes suggèrent que le désir de rendre quelque chose mutable est un argument en faveur de son appartenance à la classe, je dirais que l'inverse est vrai: si quelque chose qui existe dans le but de conserver des données va être mutable, et s'il ne sera pas évident de savoir si des instances sont attachées à quelque chose, l'objet devrait être une structure (probablement avec des champs exposés) afin de préciser que les instances ne sont pas attachées à autre chose.

Par exemple, considérons les affirmations suivantes:

  Person somePerson = myPeople.GetPerson ("123-45-6789");
  somePerson.Name = "Mary Johnson"; // avait été "Mary Smith"

La deuxième déclaration modifiera-t-elle les informations stockées myPeople? Si Personest une structure de champ exposé, ce ne sera pas le cas, et le fait que ce ne le sera pas sera une conséquence évidente de sa structure . Si Personc'est une structure et que l'on souhaite mettre à jour myPeople, il faudrait clairement faire quelque chose comme myPeople.UpdatePerson("123-45-6789", somePerson). PersonCependant, s'il s'agit d'une classe, il peut être beaucoup plus difficile de déterminer si le code ci-dessus ne mettra jamais à jour le contenu de MyPeople, ne le mettra jamais à jour ou parfois même le mettra à jour.

En ce qui concerne la notion que les structures doivent être "immuables", je suis en désaccord en général. Il existe des cas d'utilisation valables pour les structures "immuables" (où les invariants sont appliqués dans un constructeur), mais il est nécessaire de réécrire toute une structure à chaque fois qu'une partie de celle-ci change est maladroite, inutile et plus susceptible de causer des bogues que le simple fait d'exposer. champs directement. Par exemple, considérons un PhoneNumberstruct dont les champs, inclure , entre autres, AreaCodeet Exchange, et supposons que l' on a un List<PhoneNumber>. Les effets suivants devraient être assez clairs:

  pour (int i = 0; i <myList.Count; i ++)
  {
    PhoneNumber theNumber = myList [i];
    if (theNumber.AreaCode == "312")
    {
      string newExchange = "";
      if (new312to708Exchanges.TryGetValue (theNumber.Exchange), out newExchange)
      {
        theNumber.AreaCode = "708";
        theNumber.Exchange = newExchange;
        myList [i] = theNumber;
      }
    }
  }

Notez que rien dans le code ci-dessus ne connaît les champs PhoneNumberautres que AreaCodeet Exchange. S'il PhoneNumbers'agissait d'une structure dite "immuable", il faudrait soit fournir une withXXméthode pour chaque champ, ce qui renverrait une nouvelle instance de structure contenant la valeur transmise dans le champ indiqué, sinon il serait nécessaire pour le code comme ci-dessus, connaître tous les champs de la structure. Pas vraiment attrayant.

En passant, il existe au moins deux cas où les structures peuvent raisonnablement contenir des références à des types mutables.

  1. La sémantique de la structure indique qu'elle stocke l'identité de l'objet en question, plutôt qu'un raccourci pour conserver les propriétés de l'objet. Par exemple, un `KeyValuePair` conservera l’identité de certains boutons, mais ne devrait pas contenir d’informations persistantes sur la position de ces boutons, leurs états de surbrillance, etc.
  2. La structure sait qu'elle contient la seule référence à cet objet, personne d'autre n'aura de référence, et toutes les mutations qui seront jamais effectuées sur cet objet auront été effectuées avant qu'une référence à celui-ci ne soit stockée nulle part.

Dans le premier scénario, l’identité de l’objet sera immuable; dans le second cas, la classe de l'objet imbriqué peut ne pas imposer l'immuabilité, mais la structure contenant la volonté de référence.

supercat
la source
7

J'ai tendance à utiliser les structures uniquement lorsqu'une méthode est nécessaire, ce qui rend une classe trop "lourde". Ainsi, parfois, je traite les structures comme des objets légers. ATTENTION: cela ne fonctionne pas toujours et n'est pas toujours la meilleure chose à faire, alors cela dépend vraiment de la situation!

RESSOURCES SUPPLÉMENTAIRES

Pour une explication plus formelle, MSDN indique: http://msdn.microsoft.com/en-us/library/ms229017.aspx Ce lien vérifie ce que j'ai mentionné ci-dessus, les structures ne doivent être utilisées que pour héberger une petite quantité de données.

rrazd
la source
5
Les questions sur un autre site (même s'il s'agit d'un dépassement de pile ) ne sont pas considérées comme des doublons. Veuillez développer votre réponse pour inclure une réponse réelle et pas uniquement des liens vers d'autres lieux. Si les liens changent jamais, votre réponse telle qu'elle est écrite sera inutile et nous souhaitons préserver les informations et rendre les programmeurs aussi autonomes que possible. Merci!
Adam Lear
2
@Anna Lear Merci de me le faire savoir, gardez cela à l'esprit à partir de maintenant!
Rrazd
2
-1 "J'ai tendance à utiliser les structures uniquement lorsqu'une méthode est nécessaire, ce qui rend une classe trop" lourde "." ... lourd? qu'est-ce que cela signifie vraiment? ... et êtes-vous vraiment en train de dire que juste parce qu'il y a une méthode, alors ça devrait probablement être une structure?
bytedev
2

Utilisez une structure pour les constructions de données pures et une classe pour les objets avec des opérations.

Si votre structure de données ne nécessite aucun contrôle d'accès et ne comporte aucune opération spéciale autre que get / set, utilisez une structure. Cela rend évident que toute cette structure est un conteneur pour les données.

Si votre structure comporte des opérations qui modifient la structure de manière plus complexe, utilisez une classe. Une classe peut également interagir avec d'autres classes via des arguments de méthodes.

Voyez cela comme la différence entre une brique et un avion. Une brique est une structure. il a la longueur, la largeur et la hauteur. Ça ne peut pas faire grand chose. Un avion peut avoir plusieurs opérations qui le modifient d'une manière ou d'une autre, comme déplacer des gouvernes et modifier la poussée du moteur. Ce serait mieux en classe.

Michael K
la source
2
"Utiliser une structure pour les constructions de données pures et une classe pour les objets avec des opérations" en C #, cela n'a aucun sens. Voir ma réponse ci-dessous.
bytedev
1
"Utilisez une structure pour les constructions de données pures et une classe pour les objets avec des opérations." C'est vrai pour C / C ++, pas C #
taktak004