В C # почему String является ссылочным типом, который ведет себя как тип значения? - PullRequest
331 голосов
/ 12 марта 2009

String является ссылочным типом, даже если он имеет большинство характеристик типа значения, например неизменяемость и перегруженность == для сравнения текста, а не уверенность, что они ссылаются на один и тот же объект.

Почему тогда строка не является просто типом значения?

Ответы [ 12 ]

304 голосов
/ 12 марта 2009

Строки не являются типами значений, так как они могут быть огромными и должны храниться в куче. Типы значений (во всех реализациях CLR на данный момент) хранятся в стеке. Строки, выделяющие стек, могут сломать все виды вещей: размер стека составляет всего 1 МБ для 32-разрядных и 4 МБ для 64-разрядных, вам нужно будет упаковать каждую строку, что приведет к штрафу за копирование, невозможность интернировать строки и использование памяти будет шар, и т.д ...

(Редактировать: добавлено разъяснение о том, что хранилище типов значений является подробностью реализации, что приводит к такой ситуации, когда у нас есть тип со значением sematics, не наследуемый от System.ValueType. Спасибо, Бен.)

53 голосов
/ 12 марта 2009

Это не тип значения, потому что производительность (пространство и время!) Была бы ужасной, если бы это был тип значения, и его значение приходилось копировать каждый раз, когда оно передавалось и возвращалось из методов и т. Д.

Это имеет семантику значения, чтобы держать мир в здравом уме. Можете ли вы представить, как трудно было бы кодировать, если

string s = "hello";
string t = "hello";
bool b = (s == t);

установить b, чтобы быть false? Представьте себе, насколько сложным будет кодирование практически любого приложения.

23 голосов
/ 07 ноября 2013

Различие между ссылочными типами и типами значений в основном представляет собой компромисс производительности при разработке языка. У ссылочных типов есть некоторые накладные расходы на создание, уничтожение и сборку мусора, поскольку они создаются в куче. С другой стороны, типы значений накладываются на вызовы методов (если размер данных больше, чем указатель), потому что весь объект копируется, а не только указатель. Поскольку строки могут быть (и обычно имеют размер) намного больше, чем размер указателя, они разработаны как ссылочные типы. Кроме того, как указал Servy, размер типа значения должен быть известен во время компиляции, что не всегда имеет место для строк.

Вопрос изменчивости является отдельной проблемой. И ссылочные типы, и типы значений могут быть изменяемыми или неизменяемыми. Типы значений обычно являются неизменяемыми, поскольку семантика для изменяемых типов значений может сбивать с толку.

Типы ссылок, как правило, являются изменяемыми, но могут иметь неизменяемый дизайн, если это имеет смысл. Строки определены как неизменяемые, потому что это делает возможной определенную оптимизацию. Например, если один и тот же строковый литерал встречается несколько раз в одной и той же программе (что довольно часто встречается), компилятор может повторно использовать один и тот же объект.

Так почему же "==" перегружено для сравнения строк по тексту? Потому что это самая полезная семантика. Если две строки равны по тексту, они могут или не могут быть одной и той же ссылкой на объект из-за оптимизации. Поэтому сравнение ссылок довольно бесполезно, а сравнение текста - почти всегда то, что вы хотите.

Говоря более широко, Strings имеет то, что называется семантика значения . Это более общая концепция, чем типы значений, что является специфической реализацией C #. Типы значений имеют семантику значений, но ссылочные типы также могут иметь семантику значений. Когда у типа есть семантика значения, вы не можете точно сказать, является ли базовая реализация ссылочным типом или типом значения, поэтому вы можете считать это реализацией.

11 голосов
/ 23 июня 2016

Это поздний ответ на старый вопрос, но во всех остальных ответах не хватает смысла, заключающегося в том, что в .NET не было дженериков до .NET 2.0 в 2005 году.

String является ссылочным типом, а не типом значения, потому что для Microsoft было чрезвычайно важно обеспечить, чтобы строки могли наиболее эффективно храниться в неуниверсальных коллекциях , таких как System.Collection.ArrayList.

Хранение типа значения в неуниверсальной коллекции требует специального преобразования в тип object, который называется боксом. Когда CLR упаковывает тип значения, он помещает значение в System.Object и сохраняет его в управляемой куче.

Чтение значения из коллекции требует обратной операции, которая называется распаковкой.

Бокс и распаковка имеют немаловажную стоимость: для бокса требуется дополнительное выделение, для распаковки требуется проверка типа.

Некоторые ответы неверно утверждают, что string никогда не мог бы быть реализован как тип значения, потому что его размер является переменным. На самом деле, легко реализовать строку как структуру данных фиксированной длины, используя стратегию оптимизации небольших строк: строки будут храниться в памяти непосредственно как последовательность символов Юникода, за исключением больших строк, которые будут храниться в виде указателя на внешний буфер. Оба представления могут иметь одинаковую фиксированную длину, то есть размер указателя.

Если бы дженерики существовали с первого дня, я думаю, что строка в качестве типа значения, вероятно, была бы лучшим решением, с более простой семантикой, лучшим использованием памяти и лучшей локализацией кэша. List<string>, содержащий только небольшие строки, мог быть одним непрерывным блоком памяти.

8 голосов
/ 23 июня 2009

Не только строки являются неизменяемыми ссылочными типами. Многоадресные делегаты тоже. Поэтому писать

безопасно
protected void OnMyEventHandler()
{
     delegate handler = this.MyEventHandler;
     if (null != handler)
     {
        handler(this, new EventArgs());
     }
}

Я полагаю, что строки являются неизменяемыми, потому что это самый безопасный метод для работы с ними и выделения памяти. Почему они не являются типами Value? Предыдущие авторы правы в отношении размера стека и т. Д. Я также добавил бы, что создание строк ссылочных типов позволяет сэкономить на размере сборки, когда вы используете ту же самую константную строку в программе. Если вы определите

string s1 = "my string";
//some code here
string s2 = "my string";

Скорее всего, оба экземпляра константы "моя строка" будут выделены в вашей сборке только один раз.

Если вы хотите управлять строками, как обычными ссылочными типами, поместите строку в новый StringBuilder (string s). Или используйте MemoryStreams.

Если вы хотите создать библиотеку, в которой ожидается, что в ваших функциях будут передаваться огромные строки, определите параметр как StringBuilder или как Stream.

6 голосов
/ 12 марта 2009

Кроме того, как реализованы строки (разные для каждой платформы) и когда вы начинаете их соединять. Как и использование StringBuilder. Он выделяет буфер для вас, чтобы скопировать в него, как только вы достигнете конца, он выделяет еще больше памяти для вас, в надежде, что если вы сделаете большую конкатенацию, производительность не будет ограничена.

Может быть, Джон Скит может помочь здесь?

5 голосов
/ 12 марта 2009

Это в основном проблема с производительностью.

Работа со строками. Тип значения LIKE помогает при написании кода, но наличие типа BE приведет к значительному снижению производительности.

Для более детального изучения взгляните на хорошую статью о строках в .net framework.

2 голосов
/ 18 мая 2016

В очень простых словах любое значение, имеющее определенный размер, может рассматриваться как тип значения.

2 голосов
/ 12 марта 2009

Как вы можете сказать string это тип ссылки? Я не уверен, что это важно, как это реализовано. Строки в C # являются неизменяемыми, поэтому вам не нужно беспокоиться об этой проблеме.

2 голосов
/ 12 марта 2009

На самом деле строки очень мало похожи на типы значений. Начнем с того, что не все типы значений являются неизменяемыми, вы можете изменить значение типа Int32 так, как вам будет угодно, и это будет тот же адрес в стеке.

Строки являются неизменяемыми по очень веской причине, они не имеют ничего общего с ссылочным типом, но имеют много общего с управлением памятью. Просто более эффективно создавать новый объект при изменении размера строки, чем перемещать объекты в управляемой куче. Я думаю, что вы смешиваете значения / ссылочные типы и понятия неизменных объектов.

Насколько "==": Как вы сказали, "==" - это перегрузка оператора, и опять же это было реализовано по очень веской причине, чтобы сделать среду более полезной при работе со строками.

...