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

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

Почему строка не только тип значения, то?

Ответ 1

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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 (строка s). Или используйте MemoryStreams.

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

Ответ 5

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

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

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

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

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

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

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

Ответ 6

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

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

Ответ 7

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

Наличие строк типа LIKE value помогает при написании кода, но с его BE тип значения может сильно пострадать.

Для углубленного просмотра загляните в "хорошая статья" в строках .net.

Ответ 8

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

Ответ 9

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

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

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

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

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

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