Основная причина / проблема заключается в том, что разработчики спецификации CLS (которая определяет, как языки взаимодействуют с .net) не определили средство, с помощью которого члены класса могли бы указывать, что они должны вызываться напрямую, а не через callvirt, без того, чтобы вызывающая сторона выполняла проверка нулевой ссылки; и при этом это не обеспечило множество определяющих структур, которые не будут подвергаться «нормальному» боксу.
Если бы спецификация CLS определила такое средство, тогда .net мог бы последовательно следовать примеру, установленному Общей объектной моделью (COM), при которой ссылка на нулевую строку считалась семантически эквивалентной пустой строке, а для других Определяемые пользователем типы неизменяемых классов, которые должны иметь семантику значений, чтобы также определять значения по умолчанию. По сути, то, что должно было бы произойти для каждого члена String, например, Lengthбыло бы записано как-то так [InvokableOnNull()] int String Length { get { if (this==null) return 0; else return _Length;} }. Этот подход предложил бы очень хорошую семантику для вещей, которые должны вести себя как значения, но из-за проблем с реализацией их нужно хранить в куче. Самая большая трудность при таком подходе заключается в том, что семантика преобразования между такими типами Objectможет стать немного мутной.
Альтернативный подход состоял бы в том, чтобы разрешить определение специальных структурных типов, которые не наследуются, Objectа вместо этого имеют пользовательские операции упаковки и распаковки (которые преобразуются в / из некоторого другого типа класса). При таком подходе будет существовать тип класса, NullableStringкоторый ведет себя так же, как и строка, и специальный тип структуры String, который будет содержать одно закрытое поле Valueтипа String. Попытка преобразовать Stringв NullableStringили Objectвернет, Valueесли не ноль, или String.Emptyесли ноль. При попытке привести к Stringненулевой ссылке на NullableStringэкземпляр будет сохранена ссылка Value(возможно, при сохранении нуля, если длина равна нулю); приведение любой другой ссылки вызовет исключение.
Несмотря на то, что строки должны храниться в куче, концептуально нет причин, по которым они не должны вести себя как типы значений, которые имеют ненулевое значение по умолчанию. Хранение их в виде «нормальной» структуры, содержащей ссылку, было бы эффективно для кода, который использовал их как тип «строка», но добавил бы дополнительный уровень косвенности и неэффективности при приведении к «объекту». Хотя я не предполагаю, что .net добавит любую из перечисленных выше функций в более позднюю дату, возможно, разработчики будущих фреймворков могут рассмотреть возможность их включения.