Jakie są korzyści z oznaczenia pola jako „tylko do odczytu” w C #?

295

Jakie są korzyści z zadeklarowania zmiennej składowej jako tylko do odczytu? Czy to po prostu chroni przed zmianą wartości przez kogoś w trakcie cyklu życia klasy, czy też użycie tego słowa kluczowego powoduje poprawę szybkości lub wydajności?

leora
źródło
8
Dobra odpowiedź zewnętrzna: dotnetperls.com/readonly
OneWorld
3
Ciekawy. Jest to zasadniczo odpowiednik C # tego pytania Java stackoverflow.com/questions/137868/... Dyskusja tutaj jest jednak o wiele mniej gorąca ... hmm ...
RAY
7
Warto zauważyć, że readonlypola typów struktur nakładają karę wydajności w porównaniu z polami zmiennymi, które po prostu nie są zmutowane, ponieważ wywołanie dowolnego elementu readonlypola typu wartości spowoduje, że kompilator utworzy kopię pola i wywoła metodę członek na ten temat.
supercat
2
więcej na temat kary za wyniki: codeblog.jonskeet.uk/2014/07/16/…
CAD bloke

Odpowiedzi:

171

Słowo readonlykluczowe służy do deklarowania stałej zmiennej składowej, ale umożliwia obliczenie wartości w czasie wykonywania. Różni się to od stałej zadeklarowanej za pomocą constmodyfikatora, której wartość musi być ustawiona w czasie kompilacji. Za pomocą readonlymożna ustawić wartość pola w deklaracji lub w konstruktorze obiektu, którego pole jest członkiem.

Użyj go również, jeśli nie chcesz ponownie kompilować zewnętrznych bibliotek DLL, które odwołują się do stałej (ponieważ jest ona zastępowana w czasie kompilacji).

Bill jaszczurka
źródło
193

Nie sądzę, aby przy użyciu pola „tylko do odczytu” można było poprawić wydajność. Jest to po prostu sprawdzenie, czy po ukończeniu budowy obiektu nie można wskazać nowej wartości w tym polu.

Jednak „tylko do odczytu” bardzo różni się od innych typów semantyki tylko do odczytu, ponieważ jest egzekwowane w czasie wykonywania przez CLR. Słowo kluczowe readonly jest kompilowane do .initonly, co jest weryfikowalne przez CLR.

Prawdziwą zaletą tego słowa kluczowego jest generowanie niezmiennych struktur danych. Niezmienne struktury danych z definicji nie mogą być zmieniane po zbudowaniu. Ułatwia to rozumowanie zachowania struktury w czasie wykonywania. Na przykład nie ma niebezpieczeństwa przekazania niezmiennej struktury do innej losowej części kodu. Nie mogą tego nigdy zmienić, więc możesz niezawodnie programować w oparciu o tę strukturę.

Oto dobry wpis na temat jednej z korzyści niezmienności: gwintowanie

JaredPar
źródło
6
Jeśli to czytasz, element stackoverflow.com/questions/9860595/… tylko do odczytu może zostać zmodyfikowany i wygląda jak niespójne zachowanie przez .net
Akash Kava
69

Nie ma widocznych korzyści w zakresie wydajności readonly, a przynajmniej takich, o których nigdzie nie wspominałem. Służy to do robienia dokładnie tego, co sugerujesz, do zapobiegania modyfikacjom po ich zainicjowaniu.

Jest to więc korzystne, ponieważ pomaga pisać bardziej niezawodny, bardziej czytelny kod. Prawdziwa korzyść z takich rzeczy pojawia się, gdy pracujesz w zespole lub w celu konserwacji. Deklarowanie czegoś readonlypodobnego do zawarcia w kodzie umowy dotyczącej użycia tej zmiennej. Pomyśl o tym jako o dodawaniu dokumentacji w taki sam sposób, jak innych słowach kluczowych, takich jak internallub private, mówisz „ta zmienna nie powinna być modyfikowana po inicjalizacji”, a ponadto wymuszasz ją.

Jeśli więc utworzysz klasę i oznaczysz niektóre zmienne składowe readonlywedług projektu, to zapobiegniesz popełnieniu błędu przez siebie lub innego członka zespołu, gdy będą one rozszerzać lub modyfikować twoją klasę. Moim zdaniem jest to korzyść, którą warto mieć (niewielkim kosztem dodatkowej złożoności językowej, o czym doofledorfer wspomina w komentarzach).

Xiaofu
źródło
I otoh upraszcza język. Nie odmawia jednak wypłaty świadczenia.
dkretz
3
Zgadzam się, ale przypuszczam, że prawdziwa korzyść pojawia się, gdy nad kodem pracuje więcej niż jedna osoba. To tak, jakby mieć małe oświadczenie projektowe w kodzie, umowę dotyczącą jego użycia. Powinienem chyba to ująć w odpowiedzi, hehe.
Xiaofu,
7
Ta odpowiedź i dyskusja są w rzeczywistości najlepszą odpowiedzią moim zdaniem +1
Jeff Martin
@Xiaofu: Uczyniłeś mnie stałym pomysłem, że tylko hahaha jest piękne wyjaśnienie, że nikt na tym świecie nie jest w stanie wyjaśnić najgłupszego umysłu, jaki rozumiem
uczeń
Oznacza to, że w swoim kodeksie zamierzasz zachować tę wartość w żadnym momencie.
Andez
51

Mówiąc bardzo praktycznie:

Jeśli użyjesz const w dll A i dll B odwołuje się do tej const, wartość tej const zostanie skompilowana do dll B. Jeśli ponownie wdrożysz dll A z nową wartością dla tej const, dll B nadal będzie używać oryginalnej wartości.

Jeśli użyjesz tylko do odczytu w bibliotekach dll A i dll B, które tylko do odczytu, to readonly zawsze będzie sprawdzane w czasie wykonywania. Oznacza to, że jeśli wdrożysz dll A z nową wartością tylko do odczytu, dll B użyje tej nowej wartości.

Daniel Auger
źródło
4
To dobry praktyczny przykład na zrozumienie różnicy. Dzięki.
Shyju
Z drugiej strony constmoże mieć wzrost wydajności readonly. Oto nieco głębsze objaśnienie z kodem: dotnetperls.com/readonly
Dio Phung
2
Myślę, że w tej odpowiedzi brakuje największego praktycznego terminu: możliwości przechowywania wartości obliczonych w czasie wykonywania w readonlypolach. Nie można zapisać new object();w a, consta to ma sens, ponieważ nie można upiec przedmiotów innych niż wartości, takich jak odwołania do innych zestawów podczas kompilacji bez zmiany tożsamości.
binki
14

Istnieje potencjalny przypadek, w którym kompilator może przeprowadzić optymalizację wydajności w oparciu o obecność słowa kluczowego tylko do odczytu.

Ma to zastosowanie tylko wtedy, gdy pole tylko do odczytu jest również oznaczone jako statyczne . W takim przypadku kompilator JIT może założyć, że to pole statyczne nigdy się nie zmieni. Kompilator JIT może wziąć to pod uwagę podczas kompilacji metod klasy.

Typowy przykład: twoja klasa może mieć statyczne pole tylko do odczytu IsDebugLoggingEnabled, które jest inicjowane w konstruktorze (np. Na podstawie pliku konfiguracyjnego). Po skompilowaniu rzeczywistych metod JIT kompilator może pominąć całe części kodu, gdy rejestrowanie debugowania nie jest włączone.

Nie sprawdziłem, czy ta optymalizacja jest faktycznie zaimplementowana w bieżącej wersji kompilatora JIT, więc to tylko spekulacja.

Kristof Verbiest
źródło
czy jest na to źródło?
Sedat Kapanoglu
4
Obecny kompilator JIT faktycznie to implementuje i ma już wersję CLR 3.5. github.com/dotnet/coreclr/issues/1079
mirhagk 17.10.16
Nie można optymalizować pól tylko do odczytu z tego prostego powodu, że pola tylko do odczytu nie są tylko do odczytu, ale do zapisu. Są tylko wskazówką kompilatora, którą szanuje większość kompilatorów, a wartości pól tylko do odczytu można łatwo zastąpić poprzez odbicie (choć nie w częściowo zaufanym kodzie).
Christoph
9

Pamiętaj, że tylko do odczytu odnosi się tylko do samej wartości, więc jeśli używasz typu odwołania, tylko chroni tylko odniesienie przed zmianą. Stan instancji nie jest chroniony przez readonly.

Brian Rasmussen
źródło
4

Nie zapomnij o obejściu tego problemu readonly ustawić pola poza konstruktorami używającymi parametrów out.

Trochę bałagan, ale:

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
}

Dalsza dyskusja tutaj: http://www.adamjamesnaylor.com/2013/01/23/Setting-Readonly-Fields-From-Chained-Constructors.aspx

Adam Naylor
źródło
4
Pola są nadal przypisane do konstruktora. Nie można tego „obejść”. Nie ma znaczenia, czy wartości pochodzą z pojedynczych wyrażeń, z rozłożonego typu złożonego, czy są przypisywane za pomocą wywołania przez semantykę odniesienia out..
user2864740
To nawet nie próbuje odpowiedzieć na pytanie.
Sheridan
2

Zaskakujące, że tylko do odczytu może faktycznie spowodować wolniejszy kod, jak stwierdził Jon Skeet podczas testowania swojej biblioteki Noda Time. W takim przypadku test, który trwał 20 sekund, trwał tylko 4 sekundy po usunięciu readonly.

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

Neil
źródło
3
Zauważ, że jeśli pole jest typu readonly structC # 7.2, korzyść z tego, że pole jest nie tylko do odczytu, znika.
Jon Skeet
1

Jeśli masz wstępnie zdefiniowaną lub wstępnie obliczoną wartość, która musi pozostać taka sama przez cały program, powinieneś użyć stałej, ale jeśli masz wartość, którą należy podać w czasie wykonywania, ale po przypisaniu powinna pozostać taka sama w całym programie, powinieneś użyć tylko czytać. na przykład, jeśli musisz przypisać czas rozpoczęcia programu lub musisz zapisać wartość podaną przez użytkownika przy inicjalizacji obiektu i musisz ograniczyć ją przed dalszymi zmianami, powinieneś używać tylko do odczytu.

waqar Ahmed
źródło
1

Dodanie podstawowego aspektu odpowiedzi na to pytanie:

Właściwości można wyrazić tylko do odczytu, pomijając setoperatora. W większości przypadków nie trzeba dodawać readonlysłowa kluczowego do właściwości:

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

W przeciwieństwie do tego: pola potrzebują readonlysłowa kluczowego, aby osiągnąć podobny efekt:

public readonly int Foo; // a readonly field

Tak więc jedną zaletą oznaczenia pola, która readonlymoże być osiągnięcie podobnego poziomu ochrony przed zapisem jako właściwość bez setoperatora - bez konieczności zmiany pola na właściwość, jeśli z jakiegokolwiek powodu jest to pożądane.

kod14214
źródło
Czy istnieje różnica w zachowaniu między 2?
petrosmm
0

Uważaj na prywatne tablice tylko do odczytu. Jeśli zostaną one ujawnione klientowi jako obiektowi (możesz to zrobić dla interfejsu COM tak jak ja) klient może manipulować wartościami tablic. Użyj metody Clone () podczas zwracania tablicy jako obiektu.

Michał
źródło
20
Nie; wystawiać ReadOnlyCollection<T>zamiast tablicy.
SLaks,
To powinien być komentarz, a nie odpowiedź, ponieważ nie zawiera odpowiedzi na pytanie ...
Peter,
Co zabawne, powiedziano mi, żebym umieścił tego rodzaju odpowiedź, a nie komentarz, kiedy zrobiłem to w innym poście w zeszłym tygodniu.
Kyle Baran
Począwszy od 2013 r. Można używać ImmutableArray<T>, co pozwala uniknąć tworzenia boksu do interfejsu ( IReadOnlyList<T>) lub zawijania w klasie ( ReadOnlyCollection). Ma wydajność porównywalną do macierzy rodzimych: blogs.msdn.microsoft.com/dotnet/2013/06/24/…
Charles Taylor
0

WPF może przynosić korzyści w zakresie wydajności, ponieważ eliminuje potrzebę kosztownych DependencyProperties. Może to być szczególnie przydatne w przypadku kolekcji

Shane
źródło
0

Inną interesującą częścią użycia znakowania tylko do odczytu może być ochrona pola przed inicjalizacją w singletonie.

na przykład w kodzie z 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 odgrywa niewielką rolę w ochronie pola Singleton przed dwukrotnym zainicjowaniem. Innym szczegółem jest to, że dla wspomnianego scenariusza nie można użyć const, ponieważ const wymusza tworzenie w czasie kompilacji, ale singleton tworzy w czasie wykonywania.

Jurij Zaletskij
źródło
0

readonlymożna zainicjować w deklaracji lub uzyskać jej wartość tylko od konstruktora. W przeciwieństwie do consttego musi być inicjowany i deklarowany jednocześnie. readonly ma wszystko co const ma, plus inicjalizację konstruktora

kod 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
źródło