59 votes

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

Est-ce une façon correcte de déclarer des structs immuables ?

public struct Pair
{
    public readonly int x;
    public readonly int y;

    // Constructor and stuff
}

Je ne vois pas pourquoi cela poserait des problèmes, mais je voulais juste demander pour être sûr.

Dans cet exemple, j'ai utilisé des ints. Et si j'utilisais une classe à la place, mais que cette classe soit également immuable, comme ceci ? Cela devrait également fonctionner, n'est-ce pas ?

public struct Pair
{
    public readonly (immutableClass) x;
    public readonly (immutableClass) y;

    // Constructor and stuff
}

(A propos : je comprends que l'utilisation de Propriétés est plus généralisable et permet de changer, mais cette structure est destinée littéralement à juste stocker deux valeurs. C'est la question de l'immuabilité qui m'intéresse ici).

119voto

Eric Lippert Points 300275

Si vous devez utiliser des structs, il est préférable de les rendre immuables.

Rendre tous les champs en lecture seule est un excellent moyen de (1) documenter que la structure est immuable, et (2) d'empêcher les mutations accidentelles.

Cependant, il y a un problème, et par une étrange coïncidence, j'avais prévu d'en parler dans mon blog la semaine prochaine. C'est 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, c'est possible. "readonly" sur un champ struct est la déclaration qui fait des chèques sans argent sur son compte. Un struct 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 puisse se produire à "quelque chose se passe ici" et qui fasse que les assertions de débogage soient violées ? Bien 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'assertion est violée ! "this" et "p" font référence au même emplacement de stockage. L'emplacement de stockage est muté, et donc le contenu de "this" est muté car il s'agit de la même chose. La structure n'est pas en mesure de faire respecter le caractère de lecture seule de x et y car la structure ne possède pas le stockage ; le stockage est une variable locale qui est libre de muter autant qu'elle le souhaite.

Vous ne pouvez pas compter sur sur l'invariant qu'un champ en lecture seule dans une structure n'est jamais observé pour changer ; la seule chose sur laquelle vous pouvez compter est que vous ne pouvez pas écrire de code qui le change directement. Mais avec un petit travail sournois comme celui-ci, vous pouvez le modifier indirectement autant que vous voulez.

Voir également l'excellent article du blog de Joe Duffy sur cette question :

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

8voto

Compynerd255 Points 436

Depuis C# 7.2, vous pouvez désormais déclarer une structure entière comme étant 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 en tant que 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 que le compilateur effectue.

Comme indiqué dans La réponse d'Eric Lippert Mais cela n'empêche pas la structure elle-même d'être complètement réaffectée, ce qui a pour effet de modifier ses champs sous vos yeux. En passant par valeur ou en utilisant la nouvelle fonction in peut être utilisé pour éviter cela :

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

5voto

Mr47 Points 2091

Cela le rendrait immuable en effet. Je suppose que vous devriez ajouter un constructeur.
Si tous ses membres sont immuables aussi, cela le rendrait entièrement immuable. Il peut s'agir de classes ou de simples valeurs.

1voto

Joel B Fant Points 14013

Le compilateur interdit l'affectation à readonly ainsi que des propriétés en lecture seule.

Je recommande l'utilisation de propriétés en lecture seule principalement pour des raisons d'interface publique et de liaison de données (qui ne fonctionnera pas sur les champs). Si c'était mon projet, je l'exigerais si la structure/classe est publique. S'il s'agit d'une structure interne à une assemblée ou d'une classe privée, je pourrais ne pas en tenir compte dans un premier temps et les remanier en propriétés en lecture seule plus tard.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X