Dlaczego String nie jest stałą?


188

W .Net dlaczego String.Empty jest tylko do odczytu zamiast stałej? Zastanawiam się tylko, czy ktoś wie, jakie było uzasadnienie tej decyzji.


5
To pytanie może rozwiązać to pytanie, krótka odpowiedź brzmi: nikt nie wie ...
gdoron wspiera Monikę

Tak, +1 za odpowiedź Erica Lipperta, dziękuję!
travis,

Szczególnie biorąc pod uwagę, że Decimal.Zero jest const (z perspektywy użytkownika, czyli ...)
Hamish Grubijan,

Odpowiedzi:


148

Powodem static readonlyużycia zamiast tego constjest użycie niezarządzanego kodu, jak wskazał Microsoft tutaj w wersji Shared Source Common Language Infrastructure 2.0 . Plik do obejrzenia to sscli20\clr\src\bcl\system\string.cs.

Stała pusta przechowuje wartość pustego ciągu. Musimy wywołać konstruktor String, aby kompilator nie oznaczył tego jako literału.

Oznaczenie tego jako literału oznaczałoby, że nie pojawia się jako pole, do którego możemy uzyskać dostęp z natywnego.

Znalazłem te informacje z tego przydatnego artykułu w CodeProject .


Byłbym bardzo wdzięczny, gdybyś mógł wyjaśnić ten komentarz (ponieważ Jon Skeet nie mógł ...) zobacz tutaj: stackoverflow.com/questions/8462697/…
gdoron obsługuje Monikę

2
@gdoron: Moje przypuszczenie (i przypuszczenie) jest takie. Kiedy wartość jest zdefiniowana jako literał (stała), jej wartość jest wstawiana w miejsca, do których się odwołuje, podczas gdy gdy nie jest zdefiniowana jako literał, źródło wartości jest wskazywane, a rzeczywista wartość jest pobierana w czasie wykonywania. Podejrzewam, że ten ostatni może zapewnić, że prawidłowe zestawienie ciągu wystąpi między natywnym a .NET w czasie wykonywania - gdyby był literałem, być może macierzysty kompilator musiałby w jakiś sposób wciągnąć literalną wartość do swojego natywnego kodu, co prawdopodobnie nie jest wykonalny. To wszystko jest jednak domniemanie z mojej strony.
Jeff Yates,

7
Oznacza to, że należy użyć „”, a nie łańcucha. Puste dla domyślnych wartości parametrów w metodach. Co jest nieco denerwujące.
nicodemus13

17
„” może wyglądać jak błąd, podczas gdy string. Puste pokazuje celowe zamiary
Christopher Stevenson

3
@JeffYates Dodam, że fakt, że nie jest spójny, jest już denerwujący. Ludzie zobaczą resztę kodu i zastanawiają się, „dlaczego on używa tutaj” zamiast String.Empty? ”. Poważnie zastanawiam się nad nieużywaniem String.Emptyjuż tylko z tego powodu.
julealgon

24

Myślę, że jest tu wiele zamieszania i złych odpowiedzi.

Przede wszystkim constpola są staticczłonkami ( nie członkami instancji ).

Sprawdź rozdział 10.4 Stałe specyfikacji języka C #.

Mimo że stałe są uważane za elementy statyczne, deklaracja stała nie wymaga ani nie pozwala na modyfikator statyczny.

Jeśli public constczłonkowie są statyczni, nie można uznać, że stała utworzy nowy Obiekt.

Biorąc to pod uwagę, poniższe wiersze kodu robią dokładnie to samo w odniesieniu do tworzenia nowego obiektu.

public static readonly string Empty = "";
public const string Empty = "";

Oto uwaga od Microsoft, która wyjaśnia różnicę między 2:

Słowo kluczowe tylko do odczytu różni się od słowa kluczowego const. Pole const można zainicjować tylko w deklaracji pola. Pole tylko do odczytu można zainicjować w deklaracji lub w konstruktorze. Dlatego pola tylko do odczytu mogą mieć różne wartości w zależności od zastosowanego konstruktora. Ponadto, chociaż pole const jest stałą czasu kompilacji, pole tylko do odczytu może być użyte do stałych środowiska wykonawczego, ...

Uważam więc, że jedyną możliwą odpowiedzią jest odpowiedź Jeffa Yatesa.


+1 za miłe słowa i wyjaśnienia dotyczące specyfikacji C # dla const i static readonly.
Jeff Yates

17
Ponownie czytając to, nie zgadzam się z tym const stringi static readonly stringrobię to samo. Wartości stałych są podstawiane w połączonym kodzie, podczas gdy do statycznych wartości tylko do odczytu odwoływane są. Jeśli masz constbibliotekę A, która jest używana przez bibliotekę B, biblioteka B zastąpi wszystkie odwołania do tej constzmiennej jej wartością dosłowną; gdyby static readonlyzamiast niej zmienna ta byłaby przywołana, a jej wartość ustalona w czasie wykonywania.
Jeff Yates,

3
Punkt Jeffa jest ważny podczas odwoływania się do bibliotek. Jeśli ponownie skompilujesz A i rozprowadzisz go ponownie , bez ponownej kompilacji B , B nadal będzie używać starych wartości.
Mark Sowul

5
String.Empty read only instead of a constant?

Jeśli uczynisz ciąg ciągiem stałym , kompilator zostanie zastąpiony ciągiem rzeczywistym wszędzie tam, gdzie go wywołasz, i wypełnisz kod tym samym ciągiem znaków po całym, a gdy kod zostanie uruchomiony, musisz również odczytywać ten ciąg znaków z innej pamięci dane.

Jeśli pozostawisz ciąg odczytywany tylko w jednym miejscu, tak jak to jest String.Empty, program utrzyma ten sam ciąg tylko w jednym miejscu i odczyta go lub odniesie się do niego - utrzymując dane w pamięci minimum.

Również jeśli skompilujesz dowolną bibliotekę DLL przy użyciu String.Empty jako const, i z jakiegokolwiek powodu zmieni się String.Empty, wówczas skompilowana biblioteka dll nie będzie działać tak samo, ponieważ costsprawi, że wewnętrzny kod faktycznie zachowa kopię łańcucha przy każdym połączeniu.

Zobacz ten kod na przykład:

public class OneName
{
    const string cConst = "constant string";
    static string cStatic = "static string";
    readonly string cReadOnly = "read only string";

    protected void Fun()
    {
        string cAddThemAll ;

        cAddThemAll = cConst;
        cAddThemAll = cStatic ;
        cAddThemAll = cReadOnly;    
    }
}

przyjdzie przez kompilator jako:

public class OneName
{
    // note that the const exist also here !
    private const string cConst = "constant string";
    private readonly string cReadOnly;
    private static string cStatic;

    static OneName()
    {
        cStatic = "static string";
    }

    public OneName()
    {
        this.cReadOnly = "read only string";
    }

    protected void Fun()
    {
        string cAddThemAll ;

        // look here, will replace the const string everywhere is finds it.
        cAddThemAll = "constant string";
        cAddThemAll = cStatic;
        // but the read only will only get it from "one place".
        cAddThemAll = this.cReadOnly;

    }
}

i wezwanie do zgromadzenia

        cAddThemAll = cConst;
0000003e  mov         eax,dword ptr ds:[09379C0Ch] 
00000044  mov         dword ptr [ebp-44h],eax 
        cAddThemAll = cStatic ;
00000047  mov         eax,dword ptr ds:[094E8C44h] 
0000004c  mov         dword ptr [ebp-44h],eax 
        cAddThemAll = cReadOnly;
0000004f  mov         eax,dword ptr [ebp-3Ch] 
00000052  mov         eax,dword ptr [eax+0000017Ch] 
00000058  mov         dword ptr [ebp-44h],eax 

Edycja: poprawiona literówka


Oznacza to, że ciąg const musi być zawsze tworzony w instancji klasy zawierającej tę const? Wydaje się, że wtedy znacznie lepiej jest używać statycznego odczytu tylko.
berserker

@theberserker dobrze jest lepszy, ale masz wszystkie opcje do użycia.
Aristos

> wtedy skompilowana biblioteka dll nie będzie działać tak samo, ponieważ koszt powoduje, że wewnętrzny kod faktycznie zachowuje kopię ciągu przy każdym wywołaniu. @Aristos To nie do końca prawda. Po skompilowaniu kodu, do „kopii” łańcucha będzie się odwoływać w bloku TEXT pliku wykonywalnego, a cały kod będzie po prostu odwoływał się do tego samego bloku pamięci. To, co zacytowałeś w drugim kroku, jest po prostu krokiem pośrednim.
Peter Dolkens

@ user1533523 dziękuję za notatkę - zrobię test, kiedy znajdę trochę czasu, aby to sprawdzić
Aristos

Skąd masz ten kod asemblera? C # nie kompiluje się w asemblerze!
jv110

0

Ta odpowiedź istnieje dla celów historycznych.

Pierwotnie:

Ponieważ Stringjest klasą i dlatego nie może być stałą.

Rozszerzona dyskusja:

Podczas weryfikacji tej odpowiedzi opracowano wiele przydatnych okien dialogowych, a zamiast jej usuwać, treść jest odtwarzana bezpośrednio:

W .NET, (w przeciwieństwie do Java) ciąg i Ciąg są dokładnie takie same. I tak, możesz mieć stałe literału ciągów w .NET - DrJokepu 3 lutego 09 o 16:57

Czy mówisz, że klasa nie może mieć stałych? - StingyJack 3 lutego 09 o 16:58

Tak, obiekty muszą używać tylko do odczytu. Tylko struktury mogą wykonywać stałe. Myślę, że kiedy użyjesz stringzamiast Stringkompilatora, zmieni const w readonly dla ciebie. Wszystko po to, by zadowolić programistów C. - Garry Shutler 3 lutego 09 o 16:59

tvanfosson właśnie wyjaśnił to trochę bardziej gadatliwie. „X nie może być stały, ponieważ zawierająca Y jest klasą” była po prostu zbyt bezkontekstowa;) - Leonidas 3 lutego 09 '17:01

string.Empty jest właściwością statyczną, która zwraca instancję klasy String, a mianowicie pusty ciąg, a nie samą klasę string. - tvanfosson 3 lutego 09 o 17:01

Empty jest instancją tylko do odczytu (nie jest własnością) klasy String. - senfo 3 lutego 09 o 17:02

Ból głowy Nadal uważam, że mam rację, ale teraz jestem mniej pewny. Badania wymagane dziś wieczorem! - Garry Shutler 3 lutego 09 o 17:07

Pusty ciąg jest instancją klasy ciągu. Puste jest polem statycznym (nie właściwością, stoję poprawionym) w klasie String. Zasadniczo różnica między wskaźnikiem a rzeczą, na którą wskazuje. Gdyby nie było tylko do odczytu, moglibyśmy zmienić, do której instancji odnosi się pole Puste. - tvanfosson 3 lutego 09 o 17:07

Garry, nie musisz przeprowadzać żadnych badań. Pomyśl o tym. Łańcuch jest klasą. Puste jest wystąpieniem ciągu. - senfo 3 lutego 09 o 17:12

Jest coś, czego nie do końca rozumiem: jak, u licha, statyczny konstruktor klasy String może utworzyć instancję klasy String? Czy to nie jest scenariusz „kurczaka lub jajka”? - DrJokepu 3 lutego 09 o 17:12 5

Ta odpowiedź byłaby poprawna dla prawie każdej innej klasy oprócz System.String. .NET ma dużo specjalnej obudowy dla ciągów, a jedną z nich jest to, że MOŻESZ mieć stałe ciągów, po prostu spróbuj. W tym przypadku Jeff Yates ma poprawną odpowiedź. - Joel Mueller 3 lutego 09 o 19:25

Jak opisano w §7.18, wyrażenie stałe jest wyrażeniem, które można w pełni ocenić podczas kompilacji. Ponieważ jedynym sposobem utworzenia wartości innej niż null typu referencyjnego innego niż ciąg znaków jest zastosowanie nowego operatora, a ponieważ nowy operator nie jest dozwolony w wyrażeniu stałym, jedyna możliwa wartość dla stałych typów referencyjnych inny niż string jest pusty. Poprzednie dwa komentarze zostały zaczerpnięte bezpośrednio ze specyfikacji języka C # i powtórzą to, o czym wspomniał Joel Mueller. - senfo 4 lutego 09 o 15:05 5


Głosuj poprawną odpowiedź. Jeśli przejdziesz do definicji, przekonasz się, że znajduje się ona w klasie String i jest instancją klasy String. Fakt, że jest wyświetlany jako małe litery, jest magią kompilatora.
Garry Shutler

To nie ja cię oceniłem, ale w .NET, (w przeciwieństwie do Java) ciąg i String są dokładnie takie same. I tak, możesz mieć stałe literału ciągów w .NET
Tamas Czinege

10
Ta odpowiedź byłaby poprawna dla prawie każdej innej klasy oprócz System.String. .NET ma dużo specjalnej obudowy dla ciągów, a jedną z nich jest to, że MOŻESZ mieć stałe ciągów, po prostu spróbuj. W tym przypadku Jeff Yates ma poprawną odpowiedź.
Joel Mueller

7
Prawie usunąłem tę odpowiedź, ponieważ pojawiła się znacznie lepsza, ale dyskusja w tych komentarzach jest warta kontynuacji.
Garry Shutler

1
@Garry, masz szczęście, że przeczytałem twój ostatni komentarz, w przeciwnym razie również głosuję. Łańcuch ma specjalną cechę w .NET, więc zdarzenie, które jest klasą referencyjną, może być stałą.
Shimmy Weitzhandler
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.