Значение запутанного комментария выше "string.Empty" в источнике .NET/BCL?

Я пытаюсь понять, почему string.Empty есть readonly, а не const. Я видел this Post, но я не понимаю комментария, который Microsoft написал об этом. Поскольку Джон Скит написал в комментарии "Я не знаю - это не имеет большого смысла для меня, если честно..."

Общая версия Common Language Infrastructure 2.0 Release. string.cs находится в sscli20\clr\src\bcl\system\string.cs

// The Empty constant holds the empty string value.
//We need to call the String constructor so that the compiler doesn't mark this as a literal.
//Marking this as a literal would mean that it doesn't show up as a field which we can access 
//from native.
public static readonly String Empty = ""; 

Я не вижу здесь никакого вызова конструктора String и, кроме того, он помечен как буквальный "

Может ли кто-нибудь объяснить мне простой текст: что означает комментарий и почему string.Empty readonly, а не const?


Update:
Эрик Липперт прокомментировал теперь удаленный ответ:

Я попросил одного из старожилов С# пообедать об этом, и он не вспомнил конкретно, почему это решение было принято, но предположил, что это как-то связано с интернированием.

Ответ 1

Важная часть - это не то, что происходит в этом классе, но что происходит, когда другой класс использует (и ссылки) на него. Позвольте мне объяснить еще один пример:

Предположим, у вас есть Assembly1.dll, содержащий класс, объявляющий

public static const int SOME_ERROR_CODE=0x10;
public static readonly int SOME_OTHER_ERROR_CODE=0x20;

и другой класс, потребляющий это, например,

public int TryFoo() {
    try {foo();}
    catch (InvalidParameterException) {return SOME_ERROR_CODE;}
    catch (Exception) { return SOME_OTHER_ERROR_CODE;}
    return 0x00;
}

Вы компилируете свой класс в Assembly2.dll и связываете его с Assembly1.dll, как и ожидалось, ваш метод вернет 0x10 по недопустимым параметрам, 0x20 для других ошибок, 0x00 при успехе.

В частности, если вы создадите Assembly3.exe, содержащий что-то вроде

int errorcode=TryFoo();
if (errorcode==SOME_ERROR_CODE) bar();
else if (errorcode==SOME_OTHER_ERROR_CODE) baz();

Он будет работать как ожидается (после соединения с Assembly1.dll и Assembly2.dll)

Теперь, если вы получаете новую версию Assembly1.dll, которая имеет

public const int SOME_ERROR_CODE=0x11;
public readonly int SOME_OTHER_ERROR_CODE=0x21;

Если вы перекомпилируете Assembly3.exe и связали последний фрагмент с новым Assembly1.dll и неизменным Assembly2.dll, он перестанет работать как ожидалось:

bar() НЕ будет вызываться правильно: Assembly2.dll помнит LITERAL 0x20, который не является тем же литералом 0x21, что Assembly3.exe читает из Assembly1.dll

baz() будет вызываться правильно: Both Assembly2.dll и Assembly3.exe ссылаются на ссылку SYMBOL REFERENCE, называемую SOME_OTHER_ERROR_CODE, которая в обоих случаях разрешена текущей версией Assembly1.dll, поэтому в обоих случаях это 0x21.

Вкратце: a const создает a LITERAL, a readonly создает a SYMBOL REFERENCE.

LITERALS являются внутренними для фреймворка и не могут быть распределены и, таким образом, использованы собственным кодом.

So

public static readonly String Empty = ""; 

создает SYMBOL REFERENCE (сбрасывается во время первого использования вызовом Stnt cosntuctor), который может быть распределен таким образом из native, а

public static const String Empty = ""; 

создал бы литерал, который не может.