Quelles sont vos règles de base pour savoir quand utiliser des structures contre des classes? Je pense à la définition C # de ces termes, mais si votre langage a 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, comme 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 existe des cas d'utilisation plus intéressants.
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 agréable en ce sens que les structures et les classes n'ont pas de différences explicites dans la déclaration autre que le mot clé définissant; Donc, si vous sentez que vous devez "mettre à niveau" une structure vers une classe, ou inversement "rétrograder" une classe vers une structure, il s'agit principalement 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 sans paramètre par défaut).
Je dis "surtout", parce que la chose la plus importante à savoir sur les structures est que, parce que ce sont des types de valeur, les traiter comme des classes (types de référence) peut finir par être douloureux et demi. En particulier, la modification des propriétés d'une structure 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 passez l'instance à une autre méthode. Cette méthode modifie encore A et B. De retour dans la fonction appelante (celle qui a créé l'instance), les valeurs A et B de votre instance auront les valeurs qui leur sont données par 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 pas dans l'instance après avoir appelé la méthode. Qu'est-il arrivé? Eh bien, votre classe est maintenant une structure, ce qui signifie que c'est un type de valeur. Si vous passez 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, laissant l'instance initiale intacte.
Cela devient encore plus déroutant si vous deviez avoir un type de référence en tant que membre de votre structure (non interdit, mais extrêmement mauvaise pratique dans pratiquement tous les cas); la classe ne serait pas clonée (uniquement la référence de la structure à elle), donc les modifications apportées à la structure n'affecteraient pas l'objet d'origine, mais les modifications apportées à la sous-classe de la structure affecteront l'instance du code appelant. Cela peut très facilement mettre des structures mutables dans des états très incohérents qui peuvent provoquer des erreurs très loin de l'endroit où se trouve le vrai problème.
Pour cette raison, pratiquement toutes les autorités sur C # disent de toujours rendre vos structures immuables; permettre au consommateur de spécifier les valeurs des propriétés uniquement lors de la construction d'un objet et ne jamais fournir de moyen pour modifier les valeurs de cette instance. Les champs en lecture seule ou les propriétés en lecture seule sont la règle. Si le consommateur souhaite modifier la valeur, il peut créer un nouvel objet basé sur les valeurs de l'ancien, avec les modifications qu'il souhaite, ou il peut appeler une méthode qui fera de même. Cela les oblige à traiter une seule instance de votre structure comme une "valeur" conceptuelle, indivisible et distincte (mais éventuellement assimilable à) toutes les autres. S'ils effectuent une opération sur une "valeur" stockée par votre type, ils obtiennent une nouvelle "valeur" qui est différente de leur valeur initiale, mais toujours comparable et/ou sémantiquement équitable.
Pour un bon exemple, regardez le type DateTime. Vous ne pouvez pas affecter directement l'un des champs d'une instance DateTime; 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. Ce n'est pas parce que 5 + 1 = 6 que 5 est maintenant 6 parce que vous y avez ajouté 1. DateTimes fonctionne de la même manière; 12:00 ne "devient" pas 12:01 si vous ajoutez une minute, vous obtenez à la place une nouvelle valeur 12:01 qui est distincte de 12:00. S'il s'agit d'une situation logique pour votre type (les bons exemples conceptuels qui ne sont pas intégrés à .NET sont l'argent, la distance, le poids et d'autres quantités d'une UOM où les opérations doivent prendre en compte toutes les parties de la valeur), puis utilisez une structure et concevez-la en conséquence. Dans la plupart des autres cas où les sous-éléments d'un objet doivent être mutables indépendamment, utilisez une classe.
La réponse: "Utiliser une structure pour les constructions de données pures et une classe pour les objets avec des opérations" est définitivement erronée IMO. Si une structure contient un grand nombre de propriétés, une classe est presque toujours plus appropriée. Microsoft dit souvent, d'un point de vue efficacité, si votre type est supérieur à 16 octets, il doit s'agir d'une classe.
https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes
La différence la plus importante entre un class
et un struct
est ce qui se passe dans la situation suivante:
Thing thing1 = new Thing (); Thing1.somePropertyOrField = 5; Thing thing2 = thing1; Thing2.somePropertyOrField = 9;
Quel devrait être l'effet de la dernière instruction sur thing1.somePropertyOrField
? Si Thing
une structure et somePropertyOrField
est un champ public exposé, les objets thing1
Et thing2
Seront "détachés" l'un de l'autre, donc ce dernier n'affectera pas thing1
. Si Thing
est une classe, alors thing1
Et thing2
Seront attachés l'un à l'autre, et donc cette dernière instruction écrira thing1.somePropertyOrField
. On devrait utiliser une structure dans les cas où l'ancienne sémantique aurait plus de sens, et devrait utiliser une classe dans les cas où la dernière sémantique aurait plus de sens.
Notez que si certaines personnes conseillent que le désir de rendre quelque chose de mutable est un argument en faveur de ce qu'il soit la classe, je suggère que l'inverse soit vrai: si quelque chose qui existe dans le but de conserver certaines données va être mutable, et s'il n'est pas évident que les instances soient attachées à quoi que ce soit, la chose devrait être une structure (probablement avec des champs exposés) afin de préciser que les instances ne sont attachées à rien d'autre.
Par exemple, considérez les déclarations:
Personne somePerson = myPeople.GetPerson ("123-45-6789"); SomePerson.Name = "Mary Johnson"; // avait été "Mary Smith"
La deuxième instruction modifiera-t-elle les informations stockées dans myPeople
? Si Person
est une structure de champ exposé, elle ne le sera pas, et le fait qu'il ne sera pas une conséquence évidente de sa structure en champ exposé; si Person
est une structure et que l'on souhaite mettre à jour myPeople
, il faudrait clairement faire quelque chose comme myPeople.UpdatePerson("123-45-6789", somePerson)
. Si Person
est une classe, cependant, il peut être beaucoup plus difficile de déterminer si le code ci-dessus ne mettra jamais à jour le contenu de MyPeople
, le mettra toujours à jour ou parfois le mettra à jour.
En ce qui concerne la notion selon laquelle les structures devraient être "immuables", je ne suis pas d'accord en général. Il existe des cas d'utilisation valides pour les structures "immuables" (où les invariants sont appliqués dans un constructeur), mais exiger qu'une structure entière doit être réécrite à chaque fois qu'une partie de celle-ci change est maladroit, inutile et plus susceptible de provoquer des bogues que de simplement exposer champs directement. Par exemple, considérons une structure PhoneNumber
dont les champs, incluent entre autres, AreaCode
et Exchange
, et supposons que l'on a un List<PhoneNumber>
. L'effet des éléments suivants devrait être assez clair:
for (int i = 0; i <myList.Count; i ++) { PhoneNumber theNumber = myList [i]; if (theNumber.AreaCode = = "312") { Chaîne 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 ou ne se soucie des champs dans PhoneNumber
autres que AreaCode
et Exchange
. Si PhoneNumber
était une structure dite "immuable", il faudrait soit qu'elle fournisse une méthode withXX
pour chaque champ, ce qui retournerait une nouvelle instance de structure qui contenait la pass-in dans le champ indiqué, sinon il faudrait qu'un code comme celui-ci connaisse chaque champ de la structure. Pas vraiment attrayant.
BTW, il y a au moins deux cas où les structures peuvent raisonnablement contenir des références à des types mutables.
Dans le premier scénario, l'IDENTITÉ de l'objet sera immuable; dans le second, la classe de l'objet imbriqué peut ne pas imposer l'immuabilité, mais la structure contenant la référence le fera.
J'ai tendance à n'utiliser des structures que lorsqu'une seule méthode est nécessaire, ce qui fait qu'une classe semble trop "lourde". Ainsi, je traite parfois des structures comme des objets légers. ATTENTION: cela ne fonctionne pas toujours et ce n'est pas toujours la meilleure chose à faire, donc cela dépend vraiment de la situation!
RESSOURCES SUPPLÉMENTAIRES
Pour une explication plus formelle, MSDN dit: 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 un petite quantité de données.
Utilisez une structure pour les constructions de données pures et une classe pour les objets avec opérations.
Si votre structure de données n'a besoin d'aucun contrôle d'accès et n'a aucune opération spéciale autre que get/set, utilisez une structure. Cela rend évident que toute cette structure est un conteneur de 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 aux méthodes.
Considérez-le comme la différence entre une brique et un avion. Une brique est une structure; il a une longueur, une largeur et une hauteur. Ça ne peut pas faire grand-chose. Un avion peut avoir plusieurs opérations qui le mutent d'une manière ou d'une autre, comme le déplacement des gouvernes et la modification de la poussée du moteur. Ce serait mieux en tant que classe.