Was sind die Vorteile, wenn ein Feld in C # als "schreibgeschützt" markiert wird?


295

Was sind die Vorteile, wenn eine Mitgliedsvariable als schreibgeschützt deklariert wird? Schützt es nur vor jemandem, der seinen Wert während des Lebenszyklus der Klasse ändert, oder führt die Verwendung dieses Schlüsselworts zu einer Verbesserung der Geschwindigkeit oder Effizienz?


8
Gute externe Antwort: dotnetperls.com/readonly
OneWorld

3
Interessant. Dies ist im Wesentlichen das C # -Äquivalent dieser Java-Frage stackoverflow.com/questions/137868/… Die Diskussion hier ist allerdings viel weniger heiß ... hmm ...
RAY

7
Es kann erwähnenswert sein, dass readonlyFelder von Strukturtypen im Vergleich zu veränderlichen Feldern, die einfach nicht mutiert sind, eine Leistungseinbuße bedeuten, da readonlyder Compiler beim Aufrufen eines Mitglieds eines Felds vom Werttyp eine Kopie des Felds erstellt und das Feld aufruft Mitglied dazu.
Supercat

2
mehr über die Leistungsstrafe: codeblog.jonskeet.uk/2014/07/16/…
CAD Kerl

Antworten:


171

Das readonlySchlüsselwort wird verwendet, um eine Mitgliedsvariable als Konstante zu deklarieren, ermöglicht jedoch die Berechnung des Werts zur Laufzeit. Dies unterscheidet sich von einer mit dem constModifikator deklarierten Konstante , deren Wert zur Kompilierungszeit festgelegt werden muss. Mit können readonlySie den Wert des Felds entweder in der Deklaration oder im Konstruktor des Objekts festlegen, zu dem das Feld gehört.

Verwenden Sie es auch, wenn Sie keine externen DLLs neu kompilieren müssen, die auf die Konstante verweisen (da diese beim Kompilieren ersetzt werden).


193

Ich glaube nicht, dass die Verwendung eines schreibgeschützten Feldes zu Leistungssteigerungen führt. Es ist lediglich eine Überprüfung, um sicherzustellen, dass dieses Feld nicht auf einen neuen Wert gezeigt werden kann, sobald das Objekt vollständig erstellt wurde.

"Readonly" unterscheidet sich jedoch stark von anderen Arten der schreibgeschützten Semantik, da sie zur Laufzeit von der CLR erzwungen wird. Das schreibgeschützte Schlüsselwort wird auf .initonly kompiliert, was durch die CLR überprüft werden kann.

Der eigentliche Vorteil dieses Schlüsselworts besteht darin, unveränderliche Datenstrukturen zu generieren. Unveränderliche Datenstrukturen können per Definition nicht geändert werden, sobald sie erstellt wurden. Dies macht es sehr einfach, über das Verhalten einer Struktur zur Laufzeit nachzudenken. Beispielsweise besteht keine Gefahr, eine unveränderliche Struktur an einen anderen zufälligen Teil des Codes zu übergeben. Sie können es nie ändern, so dass Sie zuverlässig gegen diese Struktur programmieren können.

Hier ist ein guter Eintrag über einen der Vorteile der Unveränderlichkeit: Einfädeln


6
Wenn Sie dies lesen, kann stackoverflow.com/questions/9860595/… schreibgeschütztes Mitglied geändert werden und sieht aus wie ein inkonsistentes Verhalten von .net
Akash Kava

69

Die Verwendung readonlybietet keine offensichtlichen Leistungsvorteile , zumindest keine, die ich jemals irgendwo erwähnt habe. Dies dient nur dazu, genau das zu tun, was Sie vorschlagen, um Änderungen nach der Initialisierung zu verhindern.

Dies ist insofern von Vorteil, als Sie robusteren und besser lesbaren Code schreiben können. Der wahre Vorteil solcher Dinge liegt in der Arbeit in einem Team oder bei der Wartung. Wenn Sie etwas readonlydeklarieren, das dem Einfügen eines Vertrags für die Verwendung dieser Variablen in den Code ähnelt. Stellen Sie sich vor, Sie fügen Dokumentation auf die gleiche Weise hinzu wie andere Schlüsselwörter wie internaloder private. Sie sagen "Diese Variable sollte nach der Initialisierung nicht geändert werden" und erzwingen darüber hinaus sie außerdem.

Wenn Sie also eine Klasse erstellen und einige Mitgliedsvariablen als beabsichtigt markieren readonly, verhindern Sie, dass Sie selbst oder ein anderes Teammitglied später einen Fehler machen, wenn sie Ihre Klasse erweitern oder ändern. Meiner Meinung nach ist dies ein Vorteil, der sich lohnt (auf Kosten der zusätzlichen Komplexität der Sprache, wie doofledorfer in den Kommentaren erwähnt).


Und otoh vereinfacht die Sprache. Verweigern Sie jedoch nicht Ihre Leistungserklärung.
Dkretz

3
Ich stimme zu, aber ich nehme an, der wahre Vorteil ergibt sich, wenn mehr als eine Person an dem Code arbeitet. Es ist, als hätte man eine kleine Designerklärung im Code, einen Vertrag für dessen Verwendung. Ich sollte das wahrscheinlich in die Antwort aufnehmen, hehe.
Xiaofu

7
Diese Antwort und Diskussion ist meiner Meinung nach die beste Antwort +1
Jeff Martin

@ Xiaofu: Sie haben mich konstant gemacht von der Idee der schreibgeschützten hahaha schönen Erklärung, dass niemand auf dieser Welt den albernsten Verstand erklären und verstehen kann
Lernender

Das heißt, Sie bleiben in Ihrem Code darauf bedacht, dass sich dieser Wert zu keinem Zeitpunkt ändern sollte.
Andez

51

Um es sehr praktisch auszudrücken:

Wenn Sie eine Konstante in DLL A verwenden und DLL B auf diese Konstante verweist, wird der Wert dieser Konstante in DLL B kompiliert. Wenn Sie DLL A mit einem neuen Wert für diese Konstante erneut bereitstellen, verwendet DLL B weiterhin den ursprünglichen Wert.

Wenn Sie in dll A und dll B eine schreibgeschützte Referenz verwenden, die schreibgeschützt ist, wird diese schreibgeschützt immer zur Laufzeit nachgeschlagen. Dies bedeutet, dass DLL B diesen neuen Wert verwendet, wenn Sie DLL A mit einem neuen Wert für diesen Readonly erneut bereitstellen.


4
Dies ist ein gutes praktisches Beispiel, um den Unterschied zu verstehen. Vielen Dank.
Shyju

Auf der anderen Seite constkann es zu einem Leistungsgewinn kommen readonly. Hier ist eine etwas tiefere Erklärung mit Code: dotnetperls.com/readonly
Dio Phung

2
Ich denke, der größte praktische Begriff fehlt in dieser Antwort: die Fähigkeit, zur Laufzeit berechnete Werte in readonlyFeldern zu speichern . Sie können nicht speichern , new object();in ein constund das macht Sinn , weil Sie nicht nicht-Wert Dinge wie Verweise in andere Baugruppen während der Kompilierung backen kann , ohne Identität zu verändern.
Binki

14

Es gibt einen möglichen Fall, in dem der Compiler eine Leistungsoptimierung basierend auf dem Vorhandensein des schreibgeschützten Schlüsselworts vornehmen kann.

Dies gilt nur, wenn das schreibgeschützte Feld ebenfalls als statisch markiert ist . In diesem Fall kann der JIT-Compiler davon ausgehen, dass sich dieses statische Feld niemals ändert. Der JIT-Compiler kann dies beim Kompilieren der Methoden der Klasse berücksichtigen.

Typisches Beispiel: Ihre Klasse könnte ein statisches schreibgeschütztes IsDebugLoggingEnabled- Feld haben, das im Konstruktor initialisiert wird (z. B. basierend auf einer Konfigurationsdatei). Sobald die eigentlichen Methoden JIT-kompiliert sind, kann der Compiler ganze Teile des Codes auslassen, wenn die Debug-Protokollierung nicht aktiviert ist.

Ich habe nicht überprüft, ob diese Optimierung tatsächlich in der aktuellen Version des JIT-Compilers implementiert ist, daher handelt es sich nur um Spekulationen.


Gibt es eine Quelle dafür?
Sedat Kapanoglu

4
Der aktuelle JIT-Compiler implementiert dies tatsächlich und hat seit CLR 3.5. github.com/dotnet/coreclr/issues/1079
mirhagk

Es können keine Optimierungen für schreibgeschützte Felder vorgenommen werden, da schreibgeschützte Felder nicht schreibgeschützt, sondern schreibgeschützt sind. Sie sind nur ein Hinweis des Compilers, den die meisten Compiler respektieren, und die Werte von schreibgeschützten Feldern können leicht durch Reflexion überschrieben werden (obwohl nicht in teilweise vertrauenswürdigem Code).
Christoph

9

Beachten Sie, dass schreibgeschützt nur für den Wert selbst gilt. Wenn Sie also einen Referenztyp verwenden, schützt schreibgeschützt die Referenz nur vor Änderungen. Der Status der Instanz ist nicht schreibgeschützt.


4

Vergessen Sie nicht, dass es eine Problemumgehung gibt, mit der die readonlyFelder außerhalb von Konstruktoren festgelegt werden könnenout Parametern festzulegen.

Ein bisschen chaotisch aber:

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
}

Weitere Diskussion hier: http://www.adamjamesnaylor.com/2013/01/23/Setting-Readonly-Fields-From-Chained-Constructors.aspx


4
Die Felder werden noch im Konstruktor zugewiesen. Es gibt kein "Umgehen". Es spielt keine Rolle, ob die Werte aus einzelnen Ausdrücken stammen, aus einem zerlegten komplexen Typ oder per Aufruf durch Referenzsemantik von out..
user2864740

Dies versucht nicht einmal, die Frage zu beantworten.
Sheridan

2

Überraschenderweise kann Readonly tatsächlich zu langsamerem Code führen, wie Jon Skeet beim Testen seiner Noda Time-Bibliothek feststellte. In diesem Fall dauerte ein Test, der in 20 Sekunden ausgeführt wurde, nur 4 Sekunden, nachdem er schreibgeschützt entfernt wurde.

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


3
Beachten Sie, dass, wenn das Feld von einem Typ ist, der readonly structin C # 7.2 a ist, der Vorteil, dass das Feld nicht schreibgeschützt ist, wegfällt.
Jon Skeet

1

Wenn Sie einen vordefinierten oder vorberechneten Wert haben, der während des gesamten Programms gleich bleiben muss, sollten Sie eine Konstante verwenden. Wenn Sie jedoch einen Wert haben, der zur Laufzeit angegeben werden muss, der jedoch einmal zugewiesen wurde, sollte er während des gesamten Programms gleich bleiben schreibgeschützt. Wenn Sie beispielsweise die Programmstartzeit zuweisen müssen oder einen vom Benutzer angegebenen Wert bei der Objektinitialisierung speichern müssen und ihn auf weitere Änderungen beschränken müssen, sollten Sie schreibgeschützt verwenden.


1

Hinzufügen eines grundlegenden Aspekts zur Beantwortung dieser Frage:

Eigenschaften können als schreibgeschützt ausgedrückt werden, indem der setOperator weggelassen wird . In den meisten Fällen müssen Sie das readonlySchlüsselwort also nicht zu den Eigenschaften hinzufügen :

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

Im Gegensatz dazu: Felder benötigen das readonlySchlüsselwort, um einen ähnlichen Effekt zu erzielen:

public readonly int Foo; // a readonly field

Ein Vorteil des Markierens eines Feldes als readonlykann darin bestehen, eine ähnliche Schreibschutzstufe wie eine Eigenschaft ohne setOperator zu erreichen - ohne das Feld in eine Eigenschaft ändern zu müssen, wenn dies aus irgendeinem Grund gewünscht wird.


Gibt es einen Unterschied im Verhalten zwischen den 2?
Petrosmm

0

Seien Sie vorsichtig mit privaten schreibgeschützten Arrays. Wenn diese einen Client als Objekt verfügbar machen (Sie können dies für COM-Interop wie ich tun), kann der Client Array-Werte bearbeiten. Verwenden Sie die Clone () -Methode, wenn Sie ein Array als Objekt zurückgeben.


20
Nein; Stellen Sie ein ReadOnlyCollection<T>anstelle eines Arrays bereit.
SLaks

Dies sollte ein Kommentar sein, keine Antwort, da er keine Antwort auf die Frage liefert ...
Peter

Lustigerweise wurde mir gesagt, ich solle so etwas als Antwort und nicht als Kommentar verwenden, als ich es letzte Woche in einem anderen Beitrag tat.
Kyle Baran

Ab 2013 können Sie verwenden ImmutableArray<T>, wodurch das Boxen an eine Schnittstelle ( IReadOnlyList<T>) oder das Umschließen einer Klasse ( ReadOnlyCollection) vermieden wird . Die Leistung ist vergleichbar mit nativen Arrays: blogs.msdn.microsoft.com/dotnet/2013/06/24/…
Charles Taylor

0

WPF kann einen Leistungsvorteil bieten, da keine teuren DependencyProperties erforderlich sind. Dies kann besonders bei Sammlungen hilfreich sein


0

Ein weiterer interessanter Teil der Verwendung der schreibgeschützten Markierung kann darin bestehen, das Feld vor der Initialisierung in Singleton zu schützen.

Zum Beispiel in Code von 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()
    {
    }
}

readonly spielt eine kleine Rolle beim Schutz des Feldes Singleton vor zweimaliger Initialisierung. Ein weiteres Detail ist, dass Sie für das erwähnte Szenario keine const verwenden können, da const die Erstellung während der Kompilierungszeit erzwingt, Singleton jedoch die Erstellung zur Laufzeit vornimmt.


0

readonlykann bei der Deklaration initialisiert werden oder den Wert nur vom Konstruktor erhalten. Im Gegensatz constdazu muss es initialisiert und gleichzeitig deklariert werden. readonly hat alles const hat, plus Konstruktorinitialisierung

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 ;
        }
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.