Les structures avec des champs ou des propriétés publics mutables ne sont pas mauvaises.
Les méthodes Struct (distinctes des setters de propriétés) qui mutent "this" sont quelque peu mauvaises, uniquement parce que .net ne fournit pas un moyen de les distinguer des méthodes qui ne le font pas. Les méthodes de structure qui ne mutent pas "ceci" devraient être invocables même sur des structures en lecture seule sans avoir besoin de copie défensive. Les méthodes qui mutent "this" ne devraient pas du tout être invocables sur des structures en lecture seule. Étant donné que .net ne veut pas interdire aux méthodes de struct qui ne modifient pas "this" d'être invoquées sur des structures en lecture seule, mais ne veut pas autoriser la mutation des structures en lecture seule, il copie de manière défensive les structures dans read- seulement des contextes, obtenant sans doute le pire des deux mondes.
Malgré les problèmes de gestion des méthodes d'auto-mutation dans des contextes en lecture seule, cependant, les structures mutables offrent souvent une sémantique bien supérieure aux types de classes mutables. Considérez les trois signatures de méthode suivantes:
struct PointyStruct {public int x, y, z;};
class PointyClass {public int x, y, z;};
void Method1 (PointyStruct foo);
void Method2 (ref PointyStruct foo);
void Method3 (PointyClass foo);
Pour chaque méthode, répondez aux questions suivantes:
- En supposant que la méthode n'utilise pas de code "dangereux", pourrait-elle modifier foo?
- Si aucune référence externe à 'foo' n'existe avant l'appel de la méthode, une référence externe pourrait-elle exister après?
Réponses:
Question 1::
Method1()
non (intention claire)
Method2()
: oui (intention claire)
Method3()
: oui (intention incertaine)
Question 2
Method1()
:: non
Method2()
: non (sauf danger)
Method3()
: oui
La méthode 1 ne peut pas modifier foo et n'obtient jamais de référence. La méthode 2 obtient une référence de courte durée à foo, qu'elle peut utiliser pour modifier les champs de foo un certain nombre de fois, dans n'importe quel ordre, jusqu'à ce qu'elle revienne, mais elle ne peut pas conserver cette référence. Avant le retour de Method2, à moins qu'il n'utilise du code non sécurisé, toutes les copies qui auraient pu être faites de sa référence 'foo' auront disparu. La méthode 3, à la différence de la méthode 2, obtient une référence partageable de manière prometteuse à foo, et on ne sait pas ce qu'elle pourrait en faire. Il pourrait ne pas changer du tout foo, il pourrait changer foo puis revenir, ou il pourrait donner une référence à foo à un autre thread qui pourrait le muter d'une manière arbitraire à un moment futur arbitraire.
Les tableaux de structures offrent une merveilleuse sémantique. Étant donné RectArray [500] de type Rectangle, il est clair et évident de copier, par exemple, l'élément 123 vers l'élément 456, puis de définir ultérieurement la largeur de l'élément 123 sur 555, sans déranger l'élément 456. "RectArray [432] = RectArray [321 ]; ...; RectArray [123] .Width = 555; ". Savoir que Rectangle est une structure avec un champ entier appelé Width indiquera à tout le monde ce qu'il faut savoir sur les instructions ci-dessus.
Supposons maintenant que RectClass était une classe avec les mêmes champs que Rectangle et que l'on voulait faire les mêmes opérations sur un RectClassArray [500] de type RectClass. Peut-être que le tableau est censé contenir 500 références immuables pré-initialisées à des objets RectClass mutables. dans ce cas, le code approprié serait quelque chose comme "RectClassArray [321] .SetBounds (RectClassArray [456]); ...; RectClassArray [321] .X = 555;". Peut-être que le tableau est supposé contenir des instances qui ne vont pas changer, donc le code approprié ressemblerait plus à "RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass (RectClassArray [321 ]); RectClassArray [321] .X = 555; " Pour savoir ce que l'on est censé faire, il faudrait en savoir beaucoup plus sur RectClass (par exemple, prend-il en charge un constructeur de copie, une méthode de copie depuis, etc. ) et l'utilisation prévue de la baie. Nulle part aussi propre que d'utiliser une structure.
Pour être sûr, il n'y a malheureusement aucun moyen intéressant pour une classe de conteneur autre qu'un tableau d'offrir la sémantique propre d'un tableau struct. Le mieux que l'on puisse faire, si l'on veut qu'une collection soit indexée avec par exemple une chaîne, serait probablement de proposer une méthode générique "ActOnItem" qui accepterait une chaîne pour l'index, un paramètre générique et un délégué qui serait passé par référence à la fois au paramètre générique et à l'élément de collection. Cela permettrait presque la même sémantique que les tableaux de structures, mais à moins que les personnes vb.net et C # ne puissent être poursuivies pour offrir une belle syntaxe, le code va être maladroit même s'il est raisonnablement performant (le passage d'un paramètre générique serait permettre l'utilisation d'un délégué statique et éviterait la nécessité de créer des instances de classe temporaires).
Personnellement, je suis irrité par la haine d'Eric Lippert et al. spew concernant les types de valeurs mutables. Ils offrent une sémantique beaucoup plus propre que les types de référence promiscueux qui sont utilisés partout. Malgré certaines limitations de la prise en charge par .net des types de valeur, il existe de nombreux cas où les types de valeur mutables sont mieux adaptés que tout autre type d'entité.
int
s mutables , lesbool
s et tous les autres types de valeurs sont mauvais. Il existe des cas de mutabilité et d'immuabilité. Ces cas dépendent du rôle que jouent les données, et non du type d'allocation / partage de mémoire.