44 votes

C#, l'immuabilité et les champs en lecture seule... un mensonge ?

J'ai trouvé que les gens prétendent que l'utilisation de tous les champs en lecture seule dans une classe ne rend pas nécessairement l'instance de cette classe immuable parce qu'il y a des "moyens" de changer les valeurs des champs en lecture seule même après l'initialisation (construction).

Comment ? Quels moyens ?

Ma question est donc la suivante : quand aurons-nous un "véritable" objet immuable en C#, que je pourrai utiliser en toute sécurité dans le cadre du threading ?

De même, les types anonymes créent-ils des objets immuables ? Et certains disent que LINQ utilise des objets immuables en interne. Comment exactement ?

104voto

Eric Lippert Points 300275

Vous avez posé cinq questions là-dedans. Je vais répondre à la première :

Le fait d'avoir tous les champs en lecture seule dans une classe ne rend pas nécessairement l'instance de cette classe immuable, car il existe des "moyens" de modifier les valeurs des champs en lecture seule, même après la construction. Comment ?

Est-il possible de modifier un champ en lecture seule après sa construction ?

Oui, si vous avez suffisamment confiance en vous pour enfreindre les règles de la lecture seule. .

Comment cela fonctionne-t-il ?

Chaque bit de mémoire utilisateur dans votre processus est mutable. Des conventions telles que les champs en lecture seule peuvent donner l'impression que certains éléments sont immuables, mais si vous vous donnez du mal, vous pouvez les faire muter. Par exemple, vous pouvez prendre une instance d'objet immuable, obtenir son adresse et modifier directement les bits bruts. Cela peut nécessiter une grande ingéniosité et une connaissance des détails de la mise en œuvre interne du gestionnaire de mémoire, mais d'une manière ou d'une autre, le gestionnaire de mémoire parvient à modifier cette mémoire, donc vous le pouvez aussi si vous vous donnez la peine. Vous pouvez également utiliser la "réflexion privée" pour casser diverses parties du système de sécurité si vous avez suffisamment confiance en vous.

Par définition, un code entièrement fiable est autorisé à enfreindre les règles du système de sécurité. . C'est ce que signifie "confiance totale". Si votre code entièrement fiable choisit d'utiliser des outils tels que la réflexion privée ou le code non sécurisé pour enfreindre les règles de sécurité de la mémoire, le code entièrement fiable est autorisé à le faire.

Ne le faites pas. C'est dangereux et déroutant. Le système de sécurité de la mémoire est conçu pour faciliter le raisonnement sur l'exactitude de votre code ; violer délibérément ses règles est une mauvaise idée.

Alors, est-ce que "readonly" est un mensonge ? Eh bien, supposons que je vous dise que si tout le monde obéit aux règles tout le monde a droit à une part de gâteau. Le gâteau est-il un mensonge ? Cette affirmation est no l'affirmation "vous aurez une part de gâteau". C'est l'affirmation que si tout le monde obéit aux règles tu auras une part de gâteau. Si quelqu'un triche et prend votre part, pas de gâteau pour vous.

Un champ en lecture seule d'une classe est-il en lecture seule ? Oui mais seulement si tout le monde obéit aux règles . Les champs en lecture seule ne sont donc pas "un mensonge". Le contrat est le suivant : si tout le monde respecte les règles du système, le champ est considéré comme étant en lecture seule. Si quelqu'un enfreint les règles, alors peut-être qu'il ne l'est pas. Cela ne fait pas de l'affirmation "si tout le monde obéit aux règles, le champ est en lecture seule" un mensonge !

Une question que vous n'avez pas posée, mais qui aurait peut-être dû l'être, est de savoir si la mention "readonly" sur les champs d'une structure est également un "mensonge". Voir L'utilisation de champs publics en lecture seule pour les structures immuables fonctionne-t-elle ? pour quelques réflexions sur cette question. Les champs en lecture seule d'une structure sont beaucoup plus mensongers que les champs en lecture seule d'une classe.

Quant au reste de vos questions, je pense que vous obtiendrez de meilleurs résultats si vous posez une question par question, plutôt que cinq questions par question.

14voto

Jon Skeet Points 692016

EDIT : Je me suis concentré sur le code fonctionnant "dans" le système, par opposition à l'utilisation de la réflexion, etc. comme le mentionne Eric.

Cela dépend du type du champ. Si le champ lui-même est d'un type immuable (par ex. String ), alors c'est parfait. Si c'est StringBuilder alors l'objet peut apparaître de changer même si les champs eux-mêmes ne changent pas de valeur, car les objets "intégrés" peuvent changer.

Les types d'anonymes sont exactement les mêmes. Par exemple :

var foo = new { Bar = new StringBuilder() };
Console.WriteLine(foo); // { Bar = }
foo.Bar.Append("Hello");
Console.WriteLine(foo); // { Bar = Hello }

Donc, en gros, si vous avez un type que vous voulez être correctement immuable, vous devez vous assurer qu'il ne fait référence qu'à des données immuables.

Il y a aussi le problème des structs qui peuvent avoir des champs en lecture seule mais qui exposent quand même des méthodes qui se mutent en réassignant this . Ces structs se comportent de manière quelque peu étrange selon la situation exacte dans laquelle vous les appelez. Ce n'est pas bien - ne le faites pas.

Eric Lippert a écrit un beaucoup de choses sur l'immuabilité - c'est de l'or, comme vous pouvez vous y attendre... allez lire :) (Je n'avais pas remarqué qu'Eric était en train d'écrire une réponse à cette question lorsque j'ai écrit ce passage. Lisez évidemment sa réponse aussi)

13voto

w.brian Points 3146

Je pense que la réponse d'Eric dépasse largement le cadre de la question initiale, au point de ne même pas y répondre, alors je vais tenter ma chance :

Quoi est en lecture seule ? Eh bien, si nous parlons de types de valeurs, c'est simple : Une fois que le type de valeur est initialisé et qu'une valeur lui est attribuée, il ne peut jamais changer (du moins en ce qui concerne le compilateur).

La confusion commence à s'installer lorsque nous parlons d'utiliser readonly avec les types de référence. C'est à ce moment-là que nous devons distinguer les deux composants d'un type de référence :

  • La "référence" (variable, pointeur) qui pointe vers l'adresse en mémoire où se trouve votre objet.
  • La mémoire qui contient les données vers lesquelles la référence pointe.

Une référence à un objet est elle-même un type de valeur. Lorsque vous utilisez readonly avec un type de référence, vous rendez la référence à votre objet immuable, mais vous n'imposez pas l'immuabilité à la mémoire dans laquelle se trouve l'objet.

Considérons maintenant un objet qui contient des types de valeurs et des références à d'autres objets, et ces objets contiennent des types de valeurs et des références à d'autres objets. Si vous composez vos objets de manière à ce que tous les champs de tous les objets soient en lecture seule, vous pouvez, en substance, obtenir l'immuabilité que vous souhaitez.

8voto

Darin Dimitrov Points 528142

Readonly et Thread Safety sont deux notions différentes car Eric Lippert l'explique.

0voto

Aaron Points 2456

Peut-être que les "gens" ont entendu parler en troisième main et ont mal communiqué sur le privilège spécial que le constructeur obtient en ce qui concerne les champs "readonly". Ils ne sont pas en lecture seule.

De plus, ils ne sont pas à affectation unique. Le constructeur d'une classe peut modifier le champ autant de fois qu'il le souhaite. Le tout sans enfreindre aucune "règle".

Les constructeurs peuvent également appeler d'autres méthodes arbitraires, en se passant eux-mêmes comme paramètres. Ainsi, si vous êtes cette autre méthode, vous ne pouvez pas être certain que ce champ est encore immuable, car vous avez peut-être été invoqué par le constructeur de cet objet, et le constructeur va modifier le champ dès que vous aurez terminé.

Vous pouvez l'essayer vous-même :

using System;
class App
{
    class Foo 
    {
        readonly int x;
        public Foo() {  x = 1; Frob(); x = 2; Frob(); }
        void Frob() { Console.WriteLine(x); }
    }
    static void Main()
    {
        new Foo();
    }
}

Ce programme imprime 1, 2. 'x' est modifié après que Frob l'ait lu.

Maintenant, dans le corps d'une méthode, la valeur doit être constante -- le constructeur ne peut pas déléguer la modification de l'accès à d'autres méthodes ou délégués, donc jusqu'à ce qu'une méthode revienne, je suis presque sûr que le champ devra rester stable.

Tout ce qui précède concerne les classes. Les structures sont une toute autre histoire.

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