В C#, почему String является ссылочным типом, который ведет себя как тип значения?



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



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

1084   12  

12 ответов:

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

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

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

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

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

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

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

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

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

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

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

не только строки являются неизменяемыми ссылочными типами. делегаты Multi-cast тоже. Именно поэтому безопасно писать

protected void OnMyEventHandler()
{
     delegate handler = this.MyEventHandler;
     if (null != handler)
     {
        handler(this, new EventArgs());
     }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

не так просто, как строки состоят из массивов символов. Я смотрю на строки как массивы символов[]. Поэтому они находятся в куче, потому что ссылочная ячейка памяти хранится в стеке и указывает на начало ячейки памяти массива в куче. Размер строки не известен до ее выделения ...идеально подходит для кучи.

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

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

рискуя получить еще один таинственный голос вниз...тот факт, что многие упоминают стек и память в отношении типов значений и примитивных типов, объясняется тем, что они должны вписываться в регистр в микропроцессоре. Вы не можете нажать или поп что-то в / из стека, если он занимает больше битов, чем регистр имеет....инструкции, например, "pop eax" -потому что eax имеет ширину 32 бита в 32-разрядной системе.

примитивные типы с плавающей запятой обрабатываются FPU, который имеет 80-битные.

все это было решено задолго до того, как появился язык ООП, чтобы запутать определение примитивного типа, и я предполагаю, что тип значения-это термин, который был создан специально для языков ООП.

Comments

    Ничего не найдено.