Dlaczego parametry const nie są dozwolone w języku C #?

102

Wygląda to dziwnie, szczególnie dla programistów C ++. W C ++ oznaczaliśmy parametr jako const, aby mieć pewność, że jego stan nie zostanie zmieniony w metodzie. Istnieją również inne specyficzne powody C ++, takie jak przekazywanie const refw celu przejścia przez ref i upewnienia się, że stan nie zostanie zmieniony. Ale dlaczego nie możemy oznaczyć jako stałych parametrów metody w C #?

Dlaczego nie mogę zadeklarować mojej metody w następujący sposób?

    ....
    static void TestMethod1(const MyClass val)
    {}
    ....
    static void TestMethod2(const int val)
    {}
    ....
Incognito
źródło
W C ++ zawsze była to tylko funkcja bezpiecznego przechwytywania i nigdy nie była integralną częścią języka, tak jak go widziałem. Twórcy C # nie sądzili, że to ewidentnie dodaje wartości.
Noldorin,
3
Szersza
Jest dla typów wartości [out / ref], ale nie dla typów referencyjnych. To interesujące pytanie.
kenny
4
Jak to pytanie może mieć zarówno tagi C #, jak i tagi niezależne od języka?
CJ7
1
Niezmienne dane i funkcje bez skutków ubocznych są łatwiejsze do rozważenia. Możesz polubić
Colonel Panic

Odpowiedzi:

59

Oprócz innych dobrych odpowiedzi, dodam jeszcze jeden powód, dla którego nie należy umieszczać stałości w stylu C w C #. Powiedziałeś:

oznaczamy parametr jako const, aby mieć pewność, że jego stan nie zostanie zmieniony w metodzie.

Gdyby const faktycznie to zrobił, byłoby świetnie. Const tego nie robi. Stała to kłamstwo!

Const nie daje żadnej gwarancji, że mogę faktycznie użyć. Załóżmy, że masz metodę, która przyjmuje stałą rzecz. Jest dwóch autorów kodu: osoba pisząca dzwoniącego i osoba pisząca adres . Autor wywoływanego obiektu sprawił, że metoda przyjmuje stałą. Co obaj autorzy mogą założyć, że przedmiot jest niezmienny?

Nic. Wywoływany może odrzucić stałą i zmutować obiekt, więc wywołujący nie ma gwarancji, że wywołanie metody, która przyjmuje stałą, w rzeczywistości jej nie zmieni. Podobnie, odbiorca nie może zakładać, że zawartość przedmiotu nie zmieni się podczas działania wywoływanego; wywoływany mógłby wywołać jakąś metodę mutacji na aliasie innym niż const obiektu const, a teraz tak zwany obiekt const uległ zmianie .

Stała w stylu C nie gwarantuje, że obiekt się nie zmieni i dlatego jest zepsuta. Teraz C ma już słaby system typów, w którym możesz dokonać reinterpretacji rzutowania double na int, jeśli naprawdę chcesz, więc nie powinno być zaskoczeniem, że ma słaby system typów również w odniesieniu do const. Ale C # został zaprojektowany, aby mieć dobry system typów, system typów, w którym kiedy mówisz „ta zmienna zawiera ciąg znaków”, zmienna faktycznie zawiera odniesienie do ciągu (lub wartości null). Absolutnie nie chcemy umieszczać modyfikatora "const" w stylu C w systemie typów, ponieważ nie chcemy, aby system typów był kłamstwem . Chcemy, aby system typów był mocny , abyś mógł poprawnie uzasadnić swój kod.

Const w C to wskazówka ; w zasadzie oznacza to „możesz mi zaufać, że nie będę próbował tego modyfikować”. To nie powinno być w systemie typów ; rzeczy w systemie typów powinny być faktem o obiekcie, co do którego można uzasadnić, a nie wskazówką dotyczącą jego użycia.

Nie zrozum mnie źle; tylko dlatego, że const w C jest głęboko zerwane, nie oznacza, że ​​cała koncepcja jest bezużyteczna. To, co chciałbym zobaczyć, to rzeczywiście poprawna i użyteczna forma adnotacji „const” w C #, adnotacja, której zarówno ludzie, jak i kompilatorzy mogliby użyć, aby pomóc im zrozumieć kod, i której środowisko wykonawcze mogłoby użyć do takich rzeczy, jak automatyczna paralelizacja i inne zaawansowane optymalizacje.

Na przykład, wyobraź sobie, że możesz „narysować prostokąt” wokół kawałka kodu i powiedzieć „ Gwarantuję, że ten fragment kodu nie wykona mutacji w żadnym polu tej klasy” w sposób, który mógłby zostać sprawdzony przez kompilator. Lub narysuj ramkę, która mówi: „ta czysta metoda zmienia stan wewnętrzny obiektu, ale w żaden sposób nie jest obserwowalny poza ramką”. Taki obiekt nie mógł być bezpiecznie wielowątkowy automatycznie, ale mógłby zostać automatycznie zapamiętany . Istnieje wiele interesujących adnotacji, które moglibyśmy umieścić w kodzie, które umożliwiłyby rozbudowane optymalizacje i głębsze zrozumienie. Możemy zrobić o wiele lepiej niż słaba adnotacja const w stylu C.

Podkreślam jednak, że to tylko spekulacje . Nie mamy konkretnych planów umieszczenia tego rodzaju funkcji w żadnej hipotetycznej przyszłej wersji C #, jeśli w ogóle istnieje, której nie ogłosiliśmy w taki czy inny sposób. Jest to coś, co chciałbym zobaczyć i coś, czego może wymagać nadchodzący nacisk na przetwarzanie wielordzeniowe, ale nic z tego nie powinno być w żaden sposób interpretowane jako przewidywanie lub gwarancja jakiejkolwiek konkretnej funkcji lub przyszłego kierunku C #.

Teraz, jeśli chcesz tylko adnotacji na zmiennej lokalnej, która jest parametrem, który mówi, że „wartość tego parametru nie zmienia się w całej metodzie”, to z pewnością byłoby to łatwe. Moglibyśmy obsługiwać ustawienia lokalne i parametry „tylko do odczytu”, które byłyby inicjowane raz, oraz błąd kompilacji w czasie zmiany metody. Zmienna zadeklarowana przez instrukcję „using” jest już taką lokalną; moglibyśmy dodać opcjonalną adnotację do wszystkich lokalnych i parametrów, aby zachowywały się jak „używanie” zmiennych. Nigdy nie była to funkcja o wysokim priorytecie, więc nigdy nie została zaimplementowana.

Eric Lippert
źródło
34
Przepraszam, ale uważam ten argument za bardzo słaby, faktowi, że programista może powiedzieć kłamstwo, nie można zapobiec w żadnym języku. void foo (const A & a), które modyfikuje obiekt a, nie różni się od int countApples (Basket b), które zwracają liczbę gruszek. To tylko błąd, błąd, który można kompilować w dowolnym języku.
Alessandro Teruzzi
55
Uzytkownik może odrzucić const…” uhhhhh… (° _ °) To dość płytki argument, który tu prowadzisz. Odbierający może również po prostu rozpocząć zapisywanie losowych lokalizacji pamięci za pomocą 0xDEADBEEF. Ale oba byłyby bardzo złym programowaniem i wcześnie i przejmująco złapane przez każdy nowoczesny kompilator. W przyszłości podczas pisania odpowiedzi nie myl tego, co jest technicznie możliwe przy użyciu backdoorów języka z tym, co jest wykonalne w rzeczywistym kodzie świata. Takie wypaczone informacje dezorientują mniej doświadczonych czytelników i obniżają jakość informacji o SO.
Slipp D. Thompson
8
„Const w C to wskazówka”; Podanie źródła niektórych z rzeczy, o których mówisz, naprawdę pomogłoby w tym przypadku. Z mojego wykształcenia i doświadczenia branżowego wynika, że ​​cytowane stwierdzenie jest bzdurą - const w C jest tak samo obowiązkową wbudowaną funkcją, jak sam system typów - oba mają tylne drzwi, aby je oszukać, gdy jest to konieczne (jak naprawdę wszystko w C), ale są oczekiwane do wykorzystania przez książkę w 99,99% kodu.
Slipp D. Thompson
29
Ta odpowiedź jest powodem, dla którego czasami nienawidzę projektowania C #. Projektanci języka są absolutnie pewni, że są znacznie mądrzejsi niż inni programiści i starają się nadmiernie chronić ich przed błędami. Jest oczywiste, że słowo kluczowe const działa tylko w czasie kompilacji, ponieważ w C ++ mamy bezpośredni dostęp do pamięci i możemy zrobić wszystko, co chcemy, a także mamy wiele sposobów na obejście deklaracji const. Ale nikt nie robi tego w normalnych sytuacjach. Nawiasem mówiąc, „prywatne” i „chronione” w C # również nie dają żadnej gwarancji, ponieważ możemy uzyskać dostęp do tych metod lub pól za pomocą odbicia.
BlackOverlord
13
@BlackOverlord: (1) Chęć zaprojektowania języka, którego właściwości w naturalny sposób prowadzą programistów do sukcesu, a nie porażki, jest motywowana chęcią zbudowania przydatnych narzędzi , a nie przekonaniem, że projektanci są mądrzejsi niż ich klienci, jak przypuszczasz. (2) Odbicie nie jest częścią języka C #; jest częścią środowiska wykonawczego; dostęp do refleksji jest ograniczony przez system bezpieczeństwa środowiska wykonawczego. Kod o niskim poziomie zaufania nie daje możliwości dokonywania prywatnej refleksji. Te gwarancje przewidziane przez modyfikatory dostępu są na niskim kodu zaufania ; kod wysokiego zaufania może zrobić wszystko.
Eric Lippert
24

Jednym z powodów, dla których nie ma poprawności stałej w C #, jest to, że nie istnieje na poziomie środowiska uruchomieniowego. Pamiętaj, że C # 1.0 nie miał żadnej funkcji, chyba że była częścią środowiska uruchomieniowego.

A kilka powodów, dla których CLR nie ma pojęcia stałej poprawności, to na przykład:

  1. To komplikuje środowisko wykonawcze; poza tym JVM też go nie posiadała, a środowisko CLR zaczęło się w zasadzie jako projekt tworzenia środowiska wykonawczego podobnego do maszyny JVM, a nie środowiska wykonawczego podobnego do C ++.
  2. Jeśli istnieje stała poprawność na poziomie wykonawczym, w BCL powinna istnieć stała poprawność, w przeciwnym razie funkcja ta jest prawie bezcelowa, jeśli chodzi o .NET Framework.
  3. Ale jeśli BCL wymaga stałej poprawności, każdy język znajdujący się na szczycie CLR powinien obsługiwać stałą poprawność (VB, JavaScript, Python, Ruby, F # itp.). To się nie stanie.

Stała poprawność jest w zasadzie cechą języka obecną tylko w C ++. Tak więc w zasadzie sprowadza się to do tej samej argumentacji, dlaczego CLR nie wymaga sprawdzonych wyjątków (jest to funkcja dostępna tylko w języku Java).

Nie sądzę również, aby można było wprowadzić tak podstawową funkcję systemu typów w zarządzanym środowisku bez zerwania wstecznej kompatybilności. Więc nie licz na stałą poprawność, która kiedykolwiek wejdzie do świata C #.

Ruben
źródło
Jesteś pewien, że JVM go nie ma. Jak wiem, ma to. Możesz wypróbować public static void TestMethod (final int val) w Javie.
Incognito,
2
Finał nie jest tak naprawdę stały. Nadal możesz zmieniać pola w referencyjnym IIRC, ale nie samą wartość / odwołanie. (Który i tak jest przekazywany przez wartość, więc jest to raczej sztuczka kompilatora, która zapobiega zmianie wartości parametru).
Ruben,
1
twój trzeci punkt jest tylko częściowo prawdziwy. posiadanie stałych parametrów dla wywołań BCL nie byłoby przełomową zmianą, ponieważ tylko dokładniej opisują kontrakt. „Ta metoda nie zmieni stanu obiektu” w przeciwieństwie do „Ta metoda wymaga przekazania wartości stałej”, co byłoby zepsute
Rune FS
2
Jest wymuszane wewnątrz metody, więc wprowadzenie go do BCL nie byłoby przełomową zmianą.
Rune FS
1
Nawet jeśli środowisko wykonawcze nie może tego wymusić, pomocne byłoby posiadanie atrybutu określającego, czy funkcja powinna mieć / oczekiwać zapisu do refparametru (szczególnie w przypadku metod / właściwości struktury this). Jeśli metoda wyraźnie stwierdza, że ​​nie będzie zapisywać do refparametru, kompilator powinien zezwolić na przekazywanie wartości tylko do odczytu. I odwrotnie, jeśli metoda stwierdza, że ​​będzie zapisywać this, kompilator nie powinien zezwalać na wywoływanie takiej metody na wartościach tylko do odczytu.
supercat
12

Uważam, że istnieją dwa powody, dla których C # nie jest stała.

Pierwsza to zrozumiałość . Niewielu programistów C ++ rozumie stałą poprawność. Prosty przykład const int argjest ładny, ale widziałem też char * const * const arg- stały wskaźnik do stałych wskaźników do niestałych znaków. Stała poprawność wskaźników do funkcji to zupełnie nowy poziom zaciemniania.

Po drugie, argumenty klas są referencjami przekazywanymi przez wartość. Oznacza to, że mamy już do czynienia z dwoma poziomami stałości, bez oczywiście jasnej składni . Podobnym punktem potknięcia są kolekcje (i zbiory kolekcji itp.).

Stała poprawność jest ważną częścią systemu typów C ++. Mógłby - teoretycznie - zostać dodany do C # jako coś, co jest sprawdzane tylko w czasie kompilacji (nie musi być dodawane do CLR i nie wpłynie na BCL, chyba że uwzględniono pojęcie stałych metod składowych) .

Uważam jednak, że jest to mało prawdopodobne: druga przyczyna (składnia) byłaby dość trudna do rozwiązania, co sprawiłoby, że pierwsza przyczyna (zrozumiałość) byłaby jeszcze większym problemem.

Stephen Cleary
źródło
1
Masz rację, że CLR naprawdę nie musi się tym martwić, ponieważ można używać modopti modreqna poziomie metadanych do przechowywania tych informacji (jak C + / CLI już to robi - zobacz System.Runtime.CompilerServices.IsConst); i to można by w trywialny sposób rozszerzyć na metody const.
Pavel Minaev
Warto, ale należy to zrobić w bezpieczny sposób. Wspomniana głęboka / płytka const otwiera całą puszkę robaków.
user1496062
W c ++, w którym manipuluje się referencjami, widzę twój argument o posiadaniu dwóch typów stałych. Jednak w C # (gdzie trzeba przejść tylnymi drzwiami, aby użyć „wskaźników”) wydaje mi się wystarczająco jasne, że istnieje dobrze zdefiniowane znaczenie dla typów referencyjnych (tj. Klas) i dobrze zdefiniowane, choć inne znaczenie dla wartości typy (tj. prymitywy, struktury)
Assimilater
2
Programiści, którzy nie rozumieją ciągłości, nie powinni programować w C ++.
Steve White
5

constoznacza „stałą czasu kompilacji” w C #, a nie „tylko do odczytu, ale prawdopodobnie modyfikowalną przez inny kod”, jak w C ++. Z grubsza analog C ++ constw C # jest readonly, ale ten ma zastosowanie tylko do pól. Poza tym, w C # nie ma żadnego podobnego do C ++ pojęcia poprawności const.

Trudno powiedzieć na pewno, ponieważ potencjalnych powodów jest wiele, ale przypuszczam, że byłby to przede wszystkim chęć zachowania prostego języka i niepewne korzyści z wdrożenia tego drugiego.

Pavel Minaev
źródło
Więc myślisz, że MS uważa, że ​​„szum” ma stałe parametry w metodach.
Incognito,
1
Nie jestem pewien, co to jest „hałas” - wolałbym nazwać go „wysokim stosunkiem kosztów do korzyści”. Ale oczywiście będziesz potrzebować kogoś z zespołu C #, który powie ci na pewno.
Pavel Minaev
Tak więc, jak rozumiem, argumentujesz, że stałość została pominięta w C #, aby była prosta. Pomyśl o tym.
Steve White
2

Jest to możliwe od wersji C # 7.2 (grudzień 2017 z programem Visual Studio 2017 15.5).

Tylko dla struktur i typów podstawowych !, nie dla członków klas.

Musisz użyć in aby wysłać argument jako dane wejściowe przez odwołanie. Widzieć:

Widzieć: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/in-parameter-modifier

Na przykład:

....
static void TestMethod1(in MyStruct val)
{
    val = new MyStruct;  // Error CS8331 Cannot assign to variable 'in MyStruct' because it is a readonly variable
    val.member = 0;  // Error CS8332 Cannot assign to a member of variable 'MyStruct' because it is a readonly variable
}
....
static void TestMethod2(in int val)
{
    val = 0;  // Error CS8331 Cannot assign to variable 'in int' because it is a readonly variable
}
....
isgoed
źródło