Dlaczego C # nie zezwala na zmienne lokalne tylko do odczytu?

115

Prowadzenie przyjaznej debaty na ten temat ze współpracownikiem. Mamy kilka przemyśleń na ten temat, ale zastanawiamy się, co o tym myśli tłum SO?

Brian Genisio
źródło
4
@ColonelPanic C i C ++ mają stałe zmienne lokalne, które można zainicjować wartością obliczoną w czasie wykonywania.
Crashworks
1
JavaScript 2015 (ES6) ma typ const. Np. {Const myList = [1,2,3]; }. Używanie tej konstrukcji jest bardzo dobrą praktyką programistyczną. Więcej informacji: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
andrew.fox
1
Dla zainteresowanych istnieje sugestia UserVoice dotycząca tej funkcji . Obecnie ma tylko 87 głosów, więc jeśli chcesz zobaczyć lokalne zmienne tylko do odczytu, zrób to!
Ian Kemp,
1
To nie jest tylko problem językowy. Jest to problem większości społeczności C #, w tym najwyżej ocenianych guru języka C #, że nie dbają o poprawność const i cokolwiek z tym związanego. Opór jest bezcelowy.
Patrick Fromberg,
1
Aktualizacja 2017 : Zagłosuj na omawianą propozycję funkcji w repozytorium C # Language Design! github.com/dotnet/csharplang/issues/188
Colonel Panic

Odpowiedzi:

15

Jednym z powodów jest brak obsługi środowiska CLR dla lokalnego pliku tylko do odczytu. Tylko do odczytu jest tłumaczone na kod początkowy CLR / CLI. Ta flaga może być stosowana tylko do pól i nie ma znaczenia dla lokalnego. W rzeczywistości zastosowanie go do lokalnego prawdopodobnie spowoduje nieweryfikowalny kod.

Nie oznacza to, że C # nie mógł tego zrobić. Ale dałoby to dwa różne znaczenia tej samej konstrukcji językowej. Wersja dla lokalnych nie miałaby odpowiednika mapowania CLR.

JaredPar
źródło
57
W rzeczywistości nie ma to nic wspólnego z obsługą interfejsu wiersza polecenia dla tej funkcji, ponieważ zmienne lokalne nie są w żaden sposób ujawniane innym zestawom. Słowo readonlykluczowe dla pól musi być obsługiwane przez interfejs wiersza polecenia, ponieważ jego efekt jest widoczny dla innych zestawów. Oznaczałoby to jedynie, że zmienna ma tylko jedno przypisanie w metodzie w czasie kompilacji.
Sam Harwell,
16
Myślę, że właśnie przesunąłeś pytanie, dlaczego CLR nie obsługuje tego, zamiast zapewniać racjonalne uzasadnienie. Pozwala na stałe lokalne, więc rozsądnie byłoby oczekiwać, że tylko do odczytu lokalni.
Chad Schouggins,
9
Przykładem tego są zmienne zdefiniowane w instrukcji using. Są lokalne ... i tylko do odczytu (spróbuj je przypisać, C # doda błąd).
Softlion
7
-1 W C ++ nie ma wsparcia dla kodu maszynowego const(co w C ++ bardziej przypomina C # readonlyniż C # const, chociaż może pełnić obie role). Jednak C ++ obsługuje constlokalną zmienną automatyczną. Stąd brak obsługi CLR dla C # readonlydla zmiennej lokalnej jest nieistotny.
Pozdrawiam i hth. - Alf,
5
1. Może to być łatwo funkcja kompilatora, jak w C ++. Obsługa CLR jest całkowicie nieistotna. Zespół maszyn też go nie obsługuje, więc co? 2. (prawdopodobnie) wygenerowałby kod nieweryfikowalny - nie wiem jak, ale może się mylę. 3. dałoby dwa różne znaczenia tej samej konstrukcji językowej - wątpię, by ktokolwiek postrzegał to jako problem, skoro usingi outrobią dokładnie to, a świat się nie zawalił.
Lou
66

Myślę, że to kiepska ocena ze strony architektów C #. Modyfikator readonly na zmiennych lokalnych pomaga utrzymać poprawność programu (podobnie jak asserts) i może potencjalnie pomóc kompilatorowi w optymalizacji kodu (przynajmniej w przypadku innych języków). Fakt, że jest to obecnie niedozwolone w C #, jest kolejnym argumentem, że niektóre „funkcje” C # są jedynie wymuszeniem osobistego stylu kodowania jego twórców.

andriej
źródło
11
Zgadzam się z częścią "uratuj programistę od siebie", ale jeśli chodzi o pomoc kompilatorowi w optymalizacji kodu, jestem na stanowisku, że kompilator może bardzo dobrze dowiedzieć się, czy zmienna zmienia się w trakcie metody i odpowiednio optymalizuje tak czy inaczej. Umieszczenie flagi „tylko do odczytu” przed czymś, co optymalizator i tak rozpozna w tym celu, nie przynosi żadnych korzyści, ale może wprowadzać w błąd.
Cornelius
1
@Cornelius Zgadzam się, że istnieją opinie, że w niektórych przypadkach kompilator korzysta z diagramu przepływu danych w celu ustalenia możliwości optymalizacji niezależnie od słów kluczowych / modyfikatorów. Ale uratowanie programisty przed pisaniem niepoprawnego i w inny sposób niepotrzebnie niezoptymalizowanego kodu może otworzyć tę możliwość optymalizacji dla kompilatora.
shuva
Czy i tak nowoczesne kompilatory nie wykonują statycznego pojedynczego przypisania? W takim przypadku jest to dyskusyjne, jeśli chodzi o optymalizacje (ale jeśli kompilator obsługuje SSA, oznacza to również, że implementowanie zmiennych lokalnych przypisanych jednorazowo jest trywialne).
Dai
33

Biorąc pod uwagę odpowiedź Jareda, prawdopodobnie musiałaby to być tylko funkcja kompilacyjna - kompilator zabroniłby zapisywania do zmiennej po początkowej deklaracji (która musiałaby zawierać przypisanie).

Czy widzę w tym wartość? Potencjalnie - ale szczerze mówiąc niewiele. Jeśli nie możesz łatwo stwierdzić, czy zmienna ma zostać przypisana w innym miejscu metody, oznacza to, że Twoja metoda jest za długa.

Co więcej, Java ma tę funkcję (używając finalmodyfikatora) i bardzo rzadko widziałem ją używaną w innych przypadkach niż w przypadkach, w których musi być używana, aby umożliwić przechwytywanie zmiennej przez anonimową klasę wewnętrzną - i gdzie jest używane, sprawia wrażenie raczej bałaganu niż przydatnych informacji.

Jon Skeet
źródło
75
Istnieje różnica między sprawdzaniem, czy zmienna jest modyfikowana w Twojej metodzie przez wzrok i przez kompilator . Nie widzę sprzeciwu wobec pisania metody, stwierdzając, że nie zamierzam modyfikować zmiennej i prosząc kompilatora o powiadomienie mnie, gdy przypadkowo to zrobię (być może z literówką miesiąc później)!
A. Rex
50
Z drugiej strony w języku F # wszystkie zmienne są domyślnie tylko do odczytu i musisz użyć słowa kluczowego „mutable”, jeśli chcesz mieć możliwość ich zmiany. Ponieważ F # jest językiem .NET, wyobrażam sobie, że sprawdza on w czasie kompilacji, który opisujesz.
Joel Mueller
2
@ A.Rex: Pytanie naprawdę brzmi, czy korzyść ze skłonienia kompilatora do tego sprawdzania jest warta dodatkowego „puchu” podczas czytania kodu i nie przejmowania się nim.
Jon Skeet
3
FWIW, Scala rozróżnia lokalne readonly/ finalwartości od zmiennych za pomocą swoich słów kluczowych vali var. W kodzie Scala, lokalne vals są używane bardzo często (i faktycznie są preferowane w stosunku do lokalnych var). Podejrzewam, że główne powody, dla których finalmodyfikator nie jest częściej używany w Javie to a) bałagan i b) lenistwo.
Aaron Novstrup
4
W przypadku zmiennych lokalnych, które nie są używane w domknięciach, readonlynie byłoby to zbyt ważne. Z drugiej strony, dla zmiennych lokalnych, które są używane w domknięciach, readonlyw wielu przypadkach pozwoliłoby kompilatorowi wygenerować bardziej wydajny kod. Obecnie, gdy wykonanie wchodzi do bloku zawierającego zamknięcie, kompilator musi utworzyć nowy obiekt sterty dla zamkniętych zmiennych, nawet jeśli żaden kod, który używałby zamknięcia, nigdy nie został wykonany . Gdyby zmienna była tylko do odczytu, kod poza zamknięciem mógłby używać zwykłej zmiennej; tylko wtedy, gdy zostanie utworzony delegat na zamknięcie ...
supercat
30

Propozycja ustawień lokalnych i parametrów tylko do odczytu została krótko omówiona przez zespół projektowy C # 7. Z notatek ze spotkania projektowego w języku C # z dnia 21 stycznia 2015 r . :

Parametry i parametry lokalne mogą być przechwytywane przez lambdy i w ten sposób dostępne jednocześnie, ale nie ma sposobu, aby chronić je przed problemami ze wspólnym stanem: nie można ich tylko do odczytu.

Ogólnie rzecz biorąc, większości parametrów i wielu ustawień lokalnych nigdy nie ma być przypisanych po uzyskaniu ich wartości początkowej. Zezwolenie tylko na odczyt na nich jasno wyraziłoby ten zamiar.

Jednym z problemów jest to, że ta funkcja może być „atrakcyjną uciążliwością”. Podczas gdy „właściwą rzeczą” prawie zawsze byłoby uczynienie parametrów i lokalnych tylko odczytu, spowodowałoby to znaczne zagracenie kodu.

Pomysł, aby częściowo temu zaradzić, polega na umożliwieniu skrócenia kombinacji readonly var do zmiennej lokalnej do wartości val lub czegoś podobnego. Bardziej ogólnie moglibyśmy spróbować po prostu wymyślić krótsze słowo kluczowe niż ustalone tylko do odczytu, aby wyrazić „tylko do odczytu”.

Dyskusja jest kontynuowana w repozytorium projektu języka C #. Głosuj, aby okazać swoje poparcie. https://github.com/dotnet/csharplang/issues/188

Colonel Panic
źródło
mógłby również ustawić readonly jako domyślny (poprzez jakąś opcję, słowo kluczowe lub cokolwiek innego). większość zmiennych i parametrów powinna być tylko do odczytu, a tylko kilka z nich jest zapisywalnych. minimalizowanie liczby zapisywalnych jest generalnie dobrą rzeczą.
Dave Cousineau
12

Jest to przeoczenie dla projektanta języka C #. F # ma słowo kluczowe val i jest oparte na środowisku CLR. Nie ma powodu, dla którego C # nie może mieć tej samej funkcji języka.

Derek Liang
źródło
7

Byłem tym współpracownikiem i nie było to przyjazne! (żartuję)

Nie eliminowałbym tej funkcji, ponieważ lepiej jest pisać krótkie metody. To trochę tak, jakbyś powiedział, że nie powinieneś używać wątków, ponieważ są trudne. Daj mi nóż i pozwól mi być odpowiedzialnym za to, że się nie skaleczysz.

Osobiście potrzebowałem innego słowa kluczowego typu „var”, takiego jak „inv” (niezmienny) lub „rvar”, aby uniknąć bałaganu. Uczyłem się F # od późna i uważam, że niezmienna rzecz jest atrakcyjna.

Nigdy nie wiedziałem, że Java to ma.

Mikrofon
źródło
5

Chciałbym lokalnych zmiennych tylko do odczytu w taki sam sposób, jak lubię lokalne zmienne const . Ale ma mniejszy priorytet niż inne tematy.
Być może jego priorytetem jest ten sam powód, dla którego projektanci C # nie wdrożyli (jeszcze!) Tej funkcji. Jednak w przyszłych wersjach obsługa lokalnych zmiennych tylko do odczytu powinno być łatwe (i kompatybilne wstecz).

brgerner
źródło
2

Tylko do odczytu oznacza, że ​​jedyne miejsce, w którym można ustawić zmienną instancji, znajduje się w konstruktorze. Podczas deklarowania zmiennej lokalnie nie ma ona instancji (jest tylko w zakresie) i konstruktor nie może jej dotknąć.

Jason Punyon
źródło
6
To jest obecne znaczenie „tylko do odczytu” w C #, ale nie o to chodzi. „Tylko do odczytu” ma angielskie znaczenie, które wydaje się mieć intuicyjne zastosowanie do zmiennej lokalnej: nie można do niej pisać (po zainicjowaniu). Wydaje się to bardzo podobne do znaczenia zastosowanego do zmiennych instancji, więc dlaczego (jak sądzę w uzasadnieniu) nie możemy zastosować tego do zmiennych lokalnych?
Spike0xff
0

Wiem, to nie odpowiada na twoje pytanie dlaczego. W każdym razie osoby czytające to pytanie mogą mimo wszystko docenić poniższy kod.

Jeśli naprawdę zależy ci na strzelaniu sobie w stopę podczas nadpisywania zmiennej lokalnej, która powinna być ustawiona tylko raz, i nie chcesz, aby była ona bardziej dostępną globalnie, możesz zrobić coś takiego.

    public class ReadOnly<T>
    {
        public T Value { get; private set; }

        public ReadOnly(T pValue)
        {
            Value = pValue;
        }

        public static bool operator ==(ReadOnly<T> pReadOnlyT, T pT)
        {
            if (object.ReferenceEquals(pReadOnlyT, null))
            {
                return object.ReferenceEquals(pT, null);
            }
            return (pReadOnlyT.Value.Equals(pT));
        }

        public static bool operator !=(ReadOnly<T> pReadOnlyT, T pT)
        {
            return !(pReadOnlyT == pT);
        }
    }

Przykładowe użycie:

        var rInt = new ReadOnly<int>(5);
        if (rInt == 5)
        {
            //Int is 5 indeed
        }
        var copyValueOfInt = rInt.Value;
        //rInt.Value = 6; //Doesn't compile, setter is private

Może nie tak mniej kodu, jak rvar rInt = 5ale działa.

Mike de Klerk
źródło
To tu nie pomaga. Ten problem ze zmienną „zmienna” to: {zmienna pięć = 5 pięć = 6; Assert.That (pięć == 5)}
Murray
0

Możesz zadeklarować zmienne lokalne tylko do odczytu w C #, jeśli używasz kompilatora interaktywnego C # csi:

>"C:\Program Files (x86)\MSBuild\14.0\Bin\csi.exe"
Microsoft (R) Visual C# Interactive Compiler version 1.3.1.60616
Copyright (C) Microsoft Corporation. All rights reserved.

Type "#help" for more information.
> readonly var message = "hello";
> message = "goodbye";
(1,1): error CS0191: A readonly field cannot be assigned to (except in a constructor or a variable initializer)

Możesz także zadeklarować zmienne lokalne tylko do odczytu w .csxformacie skryptu.

Colonel Panic
źródło
5
Zgodnie z komunikatem o błędzie messagenie jest tutaj zmienną, jest ona skompilowana do pola. To nie jest szukanie dziur w dziury, ponieważ rozróżnienie wyraźnie istnieje również w interaktywnym C #: int x; Console.WriteLine(x)jest legalnym interaktywnym C # (ponieważ xjest polem i niejawnie zainicjowane), ale void foo() { int x; Console.WriteLine(x); }nie jest (ponieważ xjest zmienną i jest używane przed jej przypisaniem). Ponadto Expression<Func<int>> y = x; ((MemberExpression) y.Body).Member.MemberTypeujawni, że xjest to naprawdę pole, a nie zmienna lokalna.
Jeroen Mostert
0

c # ma już zmienną tylko do odczytu, aczkolwiek w nieco innej składni:

Rozważ następujące kwestie:

var mutable = myImmutableCalculationMethod();
readonly var immutable = mutable; // not allowed in C# 8 and prior versions
return immutable;

Porównać z:

var mutable = myImmutableCalculationMethod();
string immutable() => mutable; // allowed in C# 7
return immutable();

Trzeba przyznać, że pierwszym rozwiązaniem może być mniej kodu do napisania. Ale drugi fragment spowoduje, że odwołanie do zmiennej będzie jawne tylko do odczytu.

Wolfgang Grinfeld
źródło
To nie jest „tylko do odczytu”. To lokalna funkcja zwracająca przechwyconą zmienną ... tak wiele niepotrzebnych narzutów i brzydkiej składni, która nawet nie sprawia, że ​​podstawowa zmienna jest tylko do odczytu, z punktu widzenia dostępu do kodu. Również „zmienny” zazwyczaj odnosi się do obiektów, a nie zmienne lokalne .. rozważenia: readonly var im = new List<string>(); im.Add("read-only variable, mutable object!");.
user2864740
-2

użyj constsłowa kluczowego, aby zmienić tylko do odczytu.

odniesienie: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/const

public class SealedTest
{
    static void Main()
    {
        const int c = 707;
        Console.WriteLine("My local constant = {0}", c);
    }
}
Derek Liang
źródło
1
Interesuje nas styl JavaScript, w constktórym zmienna może być przypisana tylko podczas jej inicjalizacji, a nie styl csharp, w constktórym mogą być używane tylko wyrażenia czasu kompilacji. Np. Nie możesz zrobić, const object c = new object();ale readonlylokalny by ci na to pozwolił.
binki
-5

Myślę, że dzieje się tak dlatego, że funkcja, która ma zmienną tylko do odczytu, może nigdy nie zostać wywołana i prawdopodobnie jest w niej coś, co wykracza poza zakres, a kiedy trzeba?

scottm
źródło