Quels sont les avantages de marquer un champ comme étant en lecture seule en C #?

295

Quels sont les avantages d'avoir une variable membre déclarée en lecture seule? S'agit-il simplement d'une protection contre une modification de sa valeur pendant le cycle de vie de la classe ou l'utilisation de ce mot clé entraîne-t-elle des améliorations de vitesse ou d'efficacité?

leora
la source
8
Bonne réponse externe: dotnetperls.com/readonly
OneWorld
3
Intéressant. Ceci est essentiellement l'équivalent C # de cette question Java stackoverflow.com/questions/137868/… La discussion ici est beaucoup moins animée cependant ... hmm ...
RAY
7
Il peut être intéressant de noter que les readonlychamps de types de structure imposent une pénalité de performance par rapport aux champs mutables qui ne sont tout simplement pas mutés, car l'invocation de tout membre d'un readonlychamp de type valeur obligera le compilateur à faire une copie du champ et à appeler le Le député là-dessus.
supercat
2
plus sur la pénalité de performance: codeblog.jonskeet.uk/2014/07/16/…
CAD bloke

Réponses:

171

Le readonlymot-clé est utilisé pour déclarer une variable membre constante, mais permet de calculer la valeur au moment de l'exécution. Cela diffère d'une constante déclarée avec le constmodificateur, dont la valeur doit être définie au moment de la compilation. À l'aide de, readonlyvous pouvez définir la valeur du champ soit dans la déclaration, soit dans le constructeur de l'objet dont le champ est membre.

Utilisez-le également si vous ne voulez pas avoir à recompiler les DLL externes qui référencent la constante (car elle est remplacée au moment de la compilation).

Bill le lézard
la source
193

Je ne pense pas qu'il y ait des gains de performances en utilisant un champ en lecture seule. Il s'agit simplement d'une vérification pour s'assurer qu'une fois l'objet entièrement construit, ce champ ne peut pas pointer vers une nouvelle valeur.

Cependant, "lecture seule" est très différent des autres types de sémantique en lecture seule car il est appliqué au moment de l'exécution par le CLR. Le mot clé en lecture seule se compile en .initonly qui est vérifiable par le CLR.

Le véritable avantage de ce mot-clé est de générer des structures de données immuables. Les structures de données immuables par définition ne peuvent pas être modifiées une fois construites. Cela permet de raisonner très facilement sur le comportement d'une structure lors de l'exécution. Par exemple, il n'y a aucun danger de passer une structure immuable à une autre portion aléatoire de code. Ils ne peuvent jamais le changer afin que vous puissiez programmer de manière fiable contre cette structure.

Voici une bonne entrée sur l'un des avantages de l'immuabilité: le filetage

JaredPar
la source
6
Si vous lisez ceci, le membre en lecture seule stackoverflow.com/questions/9860595/… peut être modifié et ressemble à un comportement incohérent par .net
Akash Kava
Pouvez-vous mettre à jour le lien vers l'article sur le filetage ci-dessus? C'est cassé.
Akshay Khot
69

Il n'y a aucun avantage apparent en termes de performances à utiliser readonly, du moins aucun que je n'ai jamais vu mentionné nulle part. C'est juste pour faire exactement ce que vous suggérez, pour empêcher toute modification une fois qu'elle a été initialisée.

Il est donc avantageux en ce qu'il vous aide à écrire du code plus robuste et plus lisible. Le véritable avantage de ce genre de choses vient lorsque vous travaillez en équipe ou pour la maintenance. Déclarer quelque chose comme readonlys'apparente à mettre un contrat pour l'utilisation de cette variable dans le code. Considérez-le comme l'ajout de documentation de la même manière que d'autres mots clés comme internalou private, vous dites "cette variable ne doit pas être modifiée après l'initialisation", et en plus vous l' appliquez .

Donc, si vous créez une classe et marquez certaines variables membres readonlypar conception, vous vous empêchez, vous-même ou un autre membre de l'équipe, de faire une erreur plus tard lorsqu'ils développent ou modifient votre classe. À mon avis, c'est un avantage qui vaut la peine (au détriment de la complexité supplémentaire de la langue, comme le mentionne doofledorfer dans les commentaires).

Xiaofu
la source
Et otoh simplifie la langue. Cependant, ne niez pas votre déclaration de prestations.
dkretz
3
Je suis d'accord, mais je suppose que le véritable avantage vient lorsque plus d'une personne travaille sur le code. C'est comme avoir une petite déclaration de conception dans le code, un contrat pour son utilisation. Je devrais probablement mettre cela dans la réponse, hehe.
Xiaofu
7
Cette réponse et cette discussion sont en fait la meilleure réponse à mon avis +1
Jeff Martin
@Xiaofu: Vous m'avez rendu constant l'idée de hahaha en lecture seule une belle explication que personne dans ce monde ne pourrait expliquer à un esprit stupide et comprendre
Apprenant
C'est-à-dire que vous restez déterminé dans votre code à ce que cette valeur ne change à aucun moment.
Andez
51

Pour le dire en termes très pratiques:

Si vous utilisez une const dans la dll A et la dll B référence cette const, la valeur de cette const sera compilée dans la dll B. Si vous redéployez la dll A avec une nouvelle valeur pour cette const, la dll B utilisera toujours la valeur d'origine.

Si vous utilisez un readonly dans les références dll A et dll B qui sont en lecture seule, ce readonly sera toujours recherché lors de l'exécution. Cela signifie que si vous redéployez la DLL A avec une nouvelle valeur pour cette lecture seule, la DLL DLL utilisera cette nouvelle valeur.

Daniel Auger
la source
4
Ceci est un bon exemple pratique pour comprendre la différence. Merci.
Shyju
D'un autre côté, le constgain de performances peut être supérieur readonly. Voici une explication un peu plus approfondie avec le code: dotnetperls.com/readonly
Dio Phung
2
Je pense que le plus grand terme pratique manque dans cette réponse: la possibilité de stocker des valeurs calculées lors de l'exécution dans des readonlychamps. Vous ne pouvez pas stocker un new object();dans un constet cela a du sens car vous ne pouvez pas faire cuire des éléments sans valeur tels que des références dans d'autres assemblys pendant la compilation sans changer d'identité.
binki
14

Il existe un cas potentiel où le compilateur peut effectuer une optimisation des performances basée sur la présence du mot clé en lecture seule.

Cela ne s'applique que si le champ en lecture seule est également marqué comme statique . Dans ce cas, le compilateur JIT peut supposer que ce champ statique ne changera jamais. Le compilateur JIT peut en tenir compte lors de la compilation des méthodes de la classe.

Exemple typique: votre classe peut avoir un champ IsDebugLoggingEnabled statique en lecture seule qui est initialisé dans le constructeur (par exemple basé sur un fichier de configuration). Une fois que les méthodes réelles sont compilées JIT, le compilateur peut ommettre des parties entières du code lorsque la journalisation du débogage n'est pas activée.

Je n'ai pas vérifié si cette optimisation est réellement implémentée dans la version actuelle du compilateur JIT, il ne s'agit donc que de spéculations.

Kristof Verbiest
la source
existe-t-il une source pour cela?
Sedat Kapanoglu
4
Le compilateur JIT actuel l'implémente en fait et a depuis CLR 3.5. github.com/dotnet/coreclr/issues/1079
mirhagk
Aucune optimisation ne peut être effectuée sur les champs en lecture seule pour la simple raison que les champs en lecture seule ne sont pas en lecture seule mais en écriture. Ils ne sont qu'un indice de compilation que la plupart des compilateurs respectent et les valeurs des champs en lecture seule peuvent facilement être écrasées par la réflexion (bien qu'elles ne soient pas dans un code partiellement fiable).
Christoph
9

Gardez à l'esprit que la lecture seule s'applique uniquement à la valeur elle-même, donc si vous utilisez un type de référence en lecture seule, la référence ne sera pas modifiée. L'état de l'instance n'est pas protégé par lecture seule.

Brian Rasmussen
la source
4

N'oubliez pas qu'il existe une solution de contournement pour obtenir les readonlychamps définis en dehors de tout constructeur utilisant des outparamètres.

Un peu désordonné mais:

private readonly int _someNumber;
private readonly string _someText;

public MyClass(int someNumber) : this(data, null)
{ }

public MyClass(int someNumber, string someText)
{
    Initialise(out _someNumber, someNumber, out _someText, someText);
}

private void Initialise(out int _someNumber, int someNumber, out string _someText, string someText)
{
    //some logic
}

Plus de discussion ici: http://www.adamjamesnaylor.com/2013/01/23/Setting-Readonly-Fields-From-Chained-Constructors.aspx

Adam Naylor
la source
4
Les champs sont toujours assignés dans le constructeur .. il n'y a pas de "contournement". Peu importe si les valeurs proviennent d'expressions uniques, d'un type complexe décomposé ou attribuées par appel par sémantique de référence de out..
user2864740
Cela n'essaye même pas de répondre à la question.
Sheridan
2

Étonnamment, la lecture seule peut en fait entraîner un code plus lent, comme l'a découvert Jon Skeet lors du test de sa bibliothèque Noda Time. Dans ce cas, un test exécuté en 20 secondes n'a pris que 4 secondes après la suppression en lecture seule.

https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/

Neil
la source
3
Notez que si le champ est d'un type qui est un readonly structen C # 7.2, l'avantage de rendre le champ non en lecture seule disparaît.
Jon Skeet
1

Si vous avez une valeur prédéfinie ou pré-calculée qui doit rester la même tout au long du programme, vous devez utiliser constante, mais si vous avez une valeur qui doit être fournie au moment de l'exécution mais une fois affectée doit rester la même tout au long du programme, vous devez utiliser lecture seulement. Par exemple, si vous devez affecter l'heure de début du programme ou si vous devez stocker une valeur fournie par l'utilisateur lors de l'initialisation de l'objet et que vous devez la restreindre à d'autres modifications, vous devez l'utiliser en lecture seule.

waqar ahmed
la source
1

Ajout d'un aspect de base pour répondre à cette question:

Les propriétés peuvent être exprimées en lecture seule en omettant l' setopérateur. Donc, dans la plupart des cas, vous n'aurez pas besoin d'ajouter le readonlymot - clé aux propriétés:

public int Foo { get; }  // a readonly property

Contrairement à cela: les champs ont besoin du readonlymot - clé pour obtenir un effet similaire:

public readonly int Foo; // a readonly field

Ainsi, l'un des avantages du marquage d'un champ readonlypeut être d'obtenir un niveau de protection en écriture similaire à une propriété sans setopérateur - sans avoir à changer le champ en une propriété, si pour une raison quelconque, cela est souhaité.

code14214
la source
Y a-t-il une différence de comportement entre les 2?
petrosmm
0

Soyez prudent avec les tableaux en lecture seule privés. Si ceux-ci sont exposés à un client en tant qu'objet (vous pouvez le faire pour COM Interop comme je l'ai fait), le client peut manipuler les valeurs du tableau. Utilisez la méthode Clone () lors du retour d'un tableau en tant qu'objet.

Michael
la source
20
Non; exposer un ReadOnlyCollection<T>au lieu d'un tableau.
SLaks
Cela devrait être un commentaire et non une réponse car il ne fournit pas de réponse à la question ...
Peter
Curieusement, on m'a dit de mettre ce genre de chose comme réponse, plutôt que comme commentaire quand je l'ai fait sur un autre post la semaine dernière.
Kyle Baran
Depuis 2013, vous pouvez utiliser ImmutableArray<T>ce qui évite la boxe vers une interface ( IReadOnlyList<T>) ou le wrapping dans une classe ( ReadOnlyCollection). Ses
Charles Taylor
0

Il peut y avoir un avantage de performance dans WPF, car il supprime le besoin de DependencyProperties coûteuses. Cela peut être particulièrement utile avec les collections

Shane
la source
0

Une autre partie intéressante de l'utilisation du marquage en lecture seule peut être la protection du champ contre l'initialisation dans singleton.

par exemple dans le code de csharpindepth :

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance { get { return lazy.Value; } }

    private Singleton()
    {
    }
}

joue en lecture seule un petit rôle pour protéger le champ Singleton d'être initialisé deux fois. Un autre détail est que pour le scénario mentionné, vous ne pouvez pas utiliser const car const force la création pendant la compilation, mais singleton effectue la création au moment de l'exécution.

Yuriy Zaletskyy
la source
0

readonlypeut être initialisé lors de la déclaration ou obtenir sa valeur uniquement par le constructeur. Contrairement à constcela, il doit être initialisé et déclaré en même temps. readonly a tout const a, plus l'initialisation du constructeur

code https://repl.it/HvRU/1

using System;

class MainClass {
    public static void Main (string[] args) {

        Console.WriteLine(new Test().c);
        Console.WriteLine(new Test("Constructor").c);
        Console.WriteLine(new Test().ChangeC()); //Error A readonly field 
        // `MainClass.Test.c' cannot be assigned to (except in a constructor or a 
        // variable initializer)
    }


    public class Test {
        public readonly string c = "Hello World";
        public Test() {

        }

        public Test(string val) {
          c = val;
        }

        public string ChangeC() {
            c = "Method";
            return c ;
        }
    }
}
Mina Gabriel
la source