Question Pourquoi les structures mutables sont-elles "mauvaises"?


Suite aux discussions sur le SO, j'ai déjà lu plusieurs fois la remarque que les structures mutables sont "mauvaises" (comme dans la réponse à cette question). question).

Quel est le problème réel de la mutabilité et des structures en C #?


437
2018-01-13 23:27


origine


Réponses:


Les structures sont des types de valeur, ce qui signifie qu'elles sont copiées lorsqu'elles sont transmises.

Donc, si vous changez une copie, vous changez seulement cette copie, pas l'original et pas d'autres copies qui pourraient être autour.

Si votre structure est immuable, toutes les copies automatiques résultant de la transmission par valeur seront les mêmes.

Si vous voulez le changer, vous devez le faire consciemment en créant une nouvelle instance de la structure avec les données modifiées. (pas une copie)


267
2018-01-13 23:42



Où commencer ;-p

Le blog d'Eric Lippert est toujours bon pour un devis:

Ceci est encore une autre raison pour laquelle mutable   les types de valeur sont mauvais. Essayez de toujours   rendre les types de valeur immuables.

Tout d'abord, vous avez tendance à perdre des modifications assez facilement ... par exemple, en retirant des éléments d'une liste:

Foo foo = list[0];
foo.Name = "abc";

qu'est-ce que ça a changé? Rien d'utile ...

La même chose avec les propriétés:

myObj.SomeProperty.Size = 22; // the compiler spots this one

vous obliger à faire:

Bar bar = myObj.SomeProperty;
bar.Size = 22;
myObj.SomeProperty = bar;

moins critique, il y a un problème de taille; objets mutables tendre avoir plusieurs propriétés; encore si vous avez une structure avec deux ints, un string, une DateTime et un bool, vous pouvez très rapidement brûler beaucoup de mémoire. Avec une classe, plusieurs appelants peuvent partager une référence à la même instance (les références sont petites).


161
2018-01-13 23:31



Je ne dirais pas mal mais la mutabilité est souvent un signe de surmenage de la part du programmeur pour fournir un maximum de fonctionnalités. En réalité, cela n'est souvent pas nécessaire et, à son tour, rend l'interface plus petite, plus facile à utiliser et plus difficile à utiliser (= plus robuste).

Un exemple de ceci est la lecture / écriture et l'écriture / écriture des conflits dans des conditions de course. Celles-ci ne peuvent tout simplement pas se produire dans des structures immuables, car une écriture n'est pas une opération valide.

Aussi, je prétends que la mutabilité est presque jamais réellement nécessaire, le programmeur juste pense que ça pourrait être dans le futur. Par exemple, changer une date n’a pas de sens. Créez plutôt une nouvelle date basée sur l'ancienne. C'est une opération bon marché, donc la performance n'est pas une considération.


67
2018-01-13 23:34



Les structures muettes ne sont pas mauvaises.

Ils sont absolument nécessaires dans des conditions de haute performance. Par exemple lorsque les lignes de cache et / ou la récupération de place deviennent un goulot d'étranglement.

Je n'appellerais pas l'utilisation d'une structure immuable dans ces cas d'utilisation parfaitement valables "mal".

Je peux voir le point que la syntaxe de C # ne permet pas de distinguer l'accès d'un membre d'un type de valeur ou d'un type de référence, donc je suis tout à fait pour préférant structures immuables, qui imposent l'immuabilité, sur les structures mutables.

Cependant, au lieu de simplement qualifier les structures immuables de «mauvaises», je conseillerais d'adopter le langage et de préconiser une règle empirique plus utile et constructive.

Par exemple: "Les structures sont des types de valeur, qui sont copiés par défaut. Vous avez besoin d'une référence si vous ne souhaitez pas les copier" ou "essayez d'abord de travailler avec des structures readonly".


38
2018-06-04 08:10



Les structs avec des champs ou propriétés mutables publics ne sont pas mauvais.

Les méthodes structurelles (différentes des méthodes de détermination des propriétés) qui modifient "ceci" sont quelque peu mauvaises, uniquement parce que .net ne permet pas de les distinguer des méthodes qui ne le font pas. Les méthodes Struct qui ne muent pas "this" devraient être invocables même sur des structures en lecture seule sans avoir besoin d'une copie défensive. Les méthodes qui mutent "this" ne devraient pas être invocables du tout sur les structures en lecture seule. Comme .net ne veut pas interdire que les méthodes struct ne modifient pas "this" comme étant 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 en lecture. seulement les contextes, sans doute obtenir le pire des deux mondes.

Malgré les problèmes liés au traitement des méthodes d'auto-mutation dans des contextes en lecture seule, les structures mutables offrent souvent une sémantique bien supérieure aux types de classe mutables. Considérez les trois signatures de méthode suivantes:

struct PointyStruct {public int x, y, z;};
classe PointyClass {public int x, y, z;};

void Méthode1 (PointyStruct foo);
void Method2 (réf PointyStruct toto);
void Méthode3 (PointyClass foo);

Pour chaque méthode, répondez aux questions suivantes:

  1. En supposant que la méthode n'utilise aucun code "dangereux", pourrait-il modifier foo?
  2. 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 si dangereux)
  Method3(): Oui

Method1 ne peut pas modifier foo, et ne reçoit jamais de référence. Method2 obtient une référence de courte durée à foo, qu'il peut utiliser pour modifier les champs de foo autant de fois que nécessaire, dans n'importe quel ordre, jusqu'à ce qu'il revienne, mais il ne peut pas conserver cette référence. Avant que Method2 ne revienne, à moins d'utiliser un code non sécurisé, toutes les copies de sa référence "foo" auront disparu. Method3, à la différence de Method2, obtient une référence à fiscible, qui ne peut être divulguée. Cela ne changera peut-être pas du tout, il pourrait changer foo, puis retourner, ou donner une référence à foo à un autre thread qui pourrait le muter de manière arbitraire à un moment futur arbitraire. La seule façon de limiter ce que Method3 pourrait faire à un objet de classe mutable transmis serait d'encapsuler l'objet mutable dans un wrapper en lecture seule, ce qui est laid et encombrant.

Les tableaux de structures offrent une merveilleuse sémantique. Étant donné RectArray [500] de type Rectangle, il est clair et évident copier l'élément 123 vers l'élément 456 puis, quelque temps plus tard, définir la largeur de l'élément 123 à 555, sans perturber l'élément 456. "RectArray [432] = RectArray [321]; ...; RectArray [123] .Width = 555;" . Sachant que Rectangle est une structure avec un champ entier appelé Width, vous en saurez tout sur les instructions ci-dessus.

Maintenant, supposons 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. Le tableau est peut-être supposé 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é serait plus proche de "RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass (RectClassArray [321 ]); RectClassArray [321] .X = 555; " Pour savoir ce que l'on est censé faire, il faut en savoir beaucoup plus sur RectClass (par exemple, prend-il en charge un constructeur de copie, une méthode de copie, etc.) et l'utilisation prévue du tableau. Nulle part aussi propre que d'utiliser une structure.

Pour être sûr, il n'y a malheureusement pas de 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 voulait qu'une collection soit indexée avec p. Ex. une chaîne, serait probablement offrir 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 le paramètre générique et l'élément de collection. Cela autoriserait presque la même sémantique que les tableaux struct, mais à moins que les personnes vb.net et C # puissent être poursuivies pour offrir une syntaxe agréable, le code sera très maladroit même s'il est raisonnablement performant (le passage d'un paramètre générique autoriser l'utilisation d'un délégué statique et éviter d'avoir à créer des instances de classe temporaires).

Personnellement, je suis irrité par la haine Eric Lippert et al. vomir concernant les types de valeurs mutables. Ils offrent une sémantique beaucoup plus propre que les types de référence prometteurs utilisés partout. Malgré certaines limitations de la prise en charge des types de valeur par .net, il existe de nombreux cas où les types de valeurs mutables sont plus adaptés que tout autre type d'entité.


33
2017-09-29 03:28



Les types de valeur représentent fondamentalement des concepts immuables. Fx, cela n'a aucun sens d'avoir une valeur mathématique telle qu'un nombre entier, un vecteur, etc. et de pouvoir ensuite la modifier. Ce serait comme redéfinir le sens d'une valeur. Au lieu de changer un type de valeur, il est plus judicieux d'attribuer une autre valeur unique. Pensez au fait que les types de valeur sont comparés en comparant toutes les valeurs de ses propriétés. Le point est que si les propriétés sont les mêmes, alors c'est la même représentation universelle de cette valeur.

Comme Konrad le mentionne, cela n'a aucun sens de changer une date non plus, car la valeur représente ce point unique dans le temps et non une instance d'un objet temporel dépendant de l'état ou du contexte.

Espère que cela a un sens pour vous. Il s'agit plus du concept que vous essayez de capturer avec des types de valeur que des détails pratiques, pour sûr.


23
2018-01-13 23:52



Il y a un autre cas de coin couple qui pourrait conduire à un comportement imprévisible du point de vue des programmeurs. Voici deux d'entre eux.

  1. Types de valeurs immuables et champs en lecture seule

// Simple mutable structure. 
// Method IncrementI mutates current state.
struct Mutable
{
    public Mutable(int i) : this() 
    {
        I = i;
    }

    public void IncrementI() { I++; }

    public int I {get; private set;}
}

// Simple class that contains Mutable structure
// as readonly field
class SomeClass 
{
    public readonly Mutable mutable = new Mutable(5);
}

// Simple class that contains Mutable structure
// as ordinary (non-readonly) field
class AnotherClass 
{
    public Mutable mutable = new Mutable(5);
}

class Program
{
    void Main()
    {
        // Case 1. Mutable readonly field
        var someClass = new SomeClass();
        someClass.mutable.IncrementI();
        // still 5, not 6, because SomeClass.mutable field is readonly
        // and compiler creates temporary copy every time when you trying to
        // access this field
        Console.WriteLine(someClass.mutable.I);

        // Case 2. Mutable ordinary field
        var anotherClass = new AnotherClass();
        anotherClass.mutable.IncrementI();

        //Prints 6, because AnotherClass.mutable field is not readonly
        Console.WriteLine(anotherClass.mutable.I);
    }
}


17
2017-07-14 10:26



Si vous avez déjà programmé dans un langage comme C / C ++, les structures peuvent être utilisées comme mutables. Il suffit de les passer avec ref, et il n'y a rien qui puisse mal tourner. Le seul problème que je trouve sont les restrictions du compilateur C # et que, dans certains cas, je suis incapable de forcer la chose stupide à utiliser une référence à la structure, au lieu d'une copie (comme quand une structure fait partie d'une classe C # ).

Donc, les structures mutables ne sont pas mauvaises, C # a fabriqué le mal. J'utilise des structures mutables en C ++ tout le temps et elles sont très pratiques et intuitives. En revanche, C # m'a fait abandonner complètement les structures en tant que membres des classes à cause de la façon dont elles gèrent les objets. Leur commodité nous a coûté le nôtre.


14
2017-10-18 11:22