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


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

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


304


источник


Ответы:


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

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


279



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

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

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

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


52



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

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

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

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

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


21



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

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.


8



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

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

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

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

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

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

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


7



Also, the way strings are implemented (different for each platform) and when you start stitching them together. Like using a StringBuilder. It allocats a buffer for you to copy into, once you reach the end, it allocates even more memory for you, in the hopes that if you do a large concatenation performance won't be hindered.

Maybe Jon Skeet can help up out here?


6



It is mainly a performance issue.

Having strings behave LIKE value type helps when writing code, but having it BE a value type would make a huge performance hit.

For an in-depth look, take a peek at a nice article on strings in the .net framework.


5



How can you tell string is a reference type? I'm not sure that it matters how it is implemented. Strings in C# are immutable precisely so that you don't have to worry about this issue.


3



Actually strings have very few resemblances to value types. For starters, not all value types are immutable, you can change the value of an Int32 all you want and it it would still be the same address on the stack.

Strings are immutable for a very good reason, it has nothing to do with it being a reference type, but has a lot to do with memory management. It's just more efficient to create a new object when string size changes than to shift things around on the managed heap. I think you're mixing together value/reference types and immutable objects concepts.

As far as "==": Like you said "==" is an operator overload, and again it was implemented for a very good reason to make framework more useful when working with strings.


2