2016-05-29 5 views
2

Пару недель назад меня попросили задать вопрос C# на собеседовании. Вопрос был именно этот:Новая ссылка при конкатенации строки

string a = "Hello, "; 

for(int i = 0; i < 99999999; i++) 
{ 
    a += "world!"; 
} 

меня спросили именно, «почему это плохой метод каскадного строки?». Мой ответ был своего рода «удобочитаемость, добавление должно быть выбрано» и т. Д.

Но, по-видимому, это не так, по словам парня, который брал у меня интервью. Итак, по его словам, каждый раз, когда мы конкатенируем строку, из-за структуры CLR, в памяти создается новая ссылка. Итак, в конце следующего кода у нас будет 99999999 строковой переменной «a» в памяти.

Я думал, что объекты создаются только один раз в стеке, как только им присваивается значение (я не говорю о куче). Я знал, что выделение памяти выполняется один раз в стеке для каждого примитивного типа данных, их значения изменяются по мере необходимости и удаляются при завершении выполнения области. Это неправильно? Или, являются ли новые ссылки переменной «a», фактически созданной в стеке, каждый раз, когда она конкатенирована?

Может кто-нибудь объяснить, как это работает для стека? Большое спасибо.

+0

связаны, если не дубликат: http://stackoverflow.com/q/2365272/578411 – rene

+0

Я думаю, что мой вопрос не на самом деле дубликат. –

+0

Конечно, но я не могу себе представить, что я могу найти дубликат, который лучше подходит: как насчет этого: http://stackoverflow.com/q/10341188/578411 – rene

ответ

0

Сначала запомните эти два факта:

  • string является непреложным типа (существующие экземпляры никогда не изменяются)
  • string является ссылочным типом («ценность» из выражения string является ссылка в том месте, где экземпляр находится)

Поэтому, заявление, как:

a += "world!"; 

будет работать аналогично a = a + "world!";. Сначала он будет ссылаться на «старый» a и конкат, что старая строка со строкой "world!". Это предполагает копирование содержимого обеих старых строк в новое место памяти. Это часть «+». Затем будет переместить ссылку a, указав на старое местоположение, указывая на новое местоположение (новая конкатенированная строка). Это часть задания «=».

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

Итак, я думаю, что ваш собеседник был абсолютно прав. Цикл вашего вопроса создаст кучу (в основном очень длинных!) Строк в памяти (в куча, так как вы хотите быть техническими).

Более простой подход может быть:

string a = "Hello, " 
    + string.Concat(Enumerable.Repeat("world!", 999...)); 

Здесь мы используем string.Concat. Этот метод будет знать, что он должен будет объединить цепочку строк в одну длинную строку и может использовать какой-то расширяемый буфер (например, StringBuilder или даже указательный тип char*), чтобы убедиться, что он не создает множество «мертвые» объекты в памяти.

(Не используйте ToArray() или аналогичные, как и в string.Concat(Enumerable.Repeat("world!", 999...).ToArray()), конечно!)

+0

Большое спасибо, это было отличное объяснение. –

-1

Reference types (т. Е. Классы & строки) всегда создаются в куче. Типы значений (например, структуры) создаются в стеке и теряются, когда функция завершает выполнение.

Однако заявив, что после цикла у вас будет N объектов в памяти, это не совсем так. При каждой оценке

a += "world!"; 

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

Наконец, конечная проблема с этим кодом заключается в том, что вы считаете, что вы изменяете объект, но строки неизменяемы, то есть вы не можете реально изменить их значение после создания. Вы можете создавать только новые, и это то, что делает оператор + =. Это было бы намного более эффективно с StringBuilder, который был сделан изменчивым.

EDIT

В соответствии с просьбой, вот стек/кучу связанных осветление. Типы значений не всегда в стеке. Они находятся в стеке, когда вы объявляете их внутри тела функции:

void method() 
{ 
    int a = 1; // goes in the stack 
} 

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

+1

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

+0

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

+1

Итак, вы соглашаетесь с тем, что ваше утверждение неверно. Почему бы потом не изменить его? – Servy

0

.NET различает типы ссылок и значения. string - тип ref. Он выделяется на кучу без исключения. Время жизни контролируется GC.

Итак, в конце следующего кода у нас будет 99999999 строковой переменной «a» в памяти.

99999999 предназначено.Конечно, некоторые из них могут быть GC'ed уже.

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

Строка не является примитивным или тип значения. Они выделяются «inline» внутри чего-то другого, такого как стек, массив или внутренние объекты кучи. Они также могут быть помещены в коробки и стать истинными объектами кучи. Ничто из этого не применяется здесь.

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