L’utilisation de champs publics en lecture seule pour les structures immuables fonctionne-t-elle?

Est-ce une façon appropriée de déclarer des structures immuables?

public struct Pair { public readonly int x; public readonly int y; // Constructor and stuff } 

Je n’arrive pas à comprendre pourquoi cela pourrait poser problème, mais je voulais juste demander à être sûr.

Dans cet exemple, j’ai utilisé ints. Et si j’utilisais plutôt une classe, mais cette classe est aussi immuable, comme ça? Cela devrait bien fonctionner aussi, non?

 public struct Pair { public readonly (immutableClass) x; public readonly (immutableClass) y; // Constructor and stuff } 

(Aside: je comprends que l’utilisation de Properties est plus généralisable et permet de changer, mais cette structure est littéralement destinée à stocker deux valeurs. Je suis juste intéressé par la question de l’immuabilité ici.)

Si vous utilisez des structures, il est recommandé de les rendre immuables.

Faire tous les champs en lecture seule est un excellent moyen pour (1) documenter que la structure est immuable et (2) prévenir les mutations accidentelles.

Cependant, il y a une ride qui, dans une étrange coïncidence, je prévoyais de bloguer à propos de la semaine prochaine. C’est-à-dire que readonly sur un champ struct est un mensonge . On s’attend à ce qu’un champ en lecture seule ne puisse pas changer, mais bien sûr, il le peut. “readonly” sur un champ struct est la déclaration qui écrit des chèques sans argent dans son compte. Une structure ne possède pas son stockage, et c’est ce stockage qui peut muter.

Par exemple, prenons votre structure:

 public struct Pair { public readonly int x; public readonly int y; public Pair(int x, int y) { this.x = x; this.y = y; } public void M(ref Pair p) { int oldX = x; int oldY = y; // Something happens here Debug.Assert(x == oldX); Debug.Assert(y == oldY); } } 

Y a-t-il quelque chose qui peut arriver à “quelque chose qui se passe ici” qui provoque la violation des assertions de débogage? Sûr.

  public void M(ref Pair p) { int oldX = this.x; int oldY = this.y; p = new Pair(0, 0); Debug.Assert(this.x == oldX); Debug.Assert(this.y == oldY); } ... Pair myPair = new Pair(10, 20); myPair.M(ref myPair); 

Et maintenant que se passe-t-il? L’affirmation est violée! “this” et “p” se réfèrent au même emplacement de stockage. L’emplacement de stockage est muté, et donc le contenu de “this” est muté car ils sont identiques. La structure ne peut pas appliquer la lecture seule de x et y car la structure ne possède pas le stockage; le stockage est une variable locale libre de muter autant qu’il le souhaite.

Vous ne pouvez pas compter sur l’invariant qu’un champ readonly dans une structure ne change jamais; La seule chose sur laquelle vous pouvez compter est que vous ne pouvez pas écrire du code qui le modifie directement. Mais avec un petit travail sournois comme celui-ci, vous pouvez indirectement changer tout ce que vous voulez.

Voir aussi l’excellent article de Joe Duffy sur ce sujet:

http://joeduffyblog.com/2010/07/01/when-is-a-readonly-field-not-readonly/

Cela le rendrait immuable. Je suppose que vous feriez mieux d’append un constructeur si.
Si tous ses membres sont immuables, cela le rendrait totalement immuable. Celles-ci peuvent être des classes ou des valeurs simples.

Le compilateur interdira l’affectation aux champs en readonly ainsi qu’aux propriétés en lecture seule.

Je recommande d’utiliser des propriétés en lecture seule principalement pour des raisons d’interface publique et de liaison de données (qui ne fonctionneront pas sur les champs). Si c’était mon projet, je demanderais que si la structure / classe est publique. Si cela doit être interne à un assembly ou privé à une classe, je pourrais tout d’abord le négliger et le transformer en propriétés en lecture seule ultérieurement.

A partir de C # 7.2, vous pouvez maintenant déclarer une structure entière comme immuable:

 public readonly struct Pair { public int x; public int y; // Constructor and stuff } 

Cela aura le même effet que de marquer tous les champs comme en readonly , et documentera également au compilateur lui-même que la structure est immuable. Cela augmentera les performances des zones où la structure est utilisée en réduisant le nombre de copies défensives effectuées par le compilateur.

Comme indiqué dans la réponse d’Eric Lippert , cela n’empêche pas que la structure elle-même soit complètement réaffectée, ce qui a pour effet de transformer ses champs sous vous. Le passage par valeur ou l’utilisation du modificateur de paramètre new in peut être utilisé pour empêcher cela:

 public void DoSomething(in Pair p) { px = 0; // illegal p = new Pair(0, 0); // also illegal }