Конкатенация строк небезопасна в C #, нужно использовать StringBuilder?

У меня такой вопрос: безопасна ли конкатенация строк в C #? Если конкатенация строк приводит к неожиданным ошибкам, а замена этой конкатенации строк с помощью StringBuilder приводит к исчезновению этих ошибок, на что это может указывать?

Предпосылки: я разрабатываю небольшое приложение на C # для командной строки. Он принимает аргументы командной строки, выполняет слегка сложный SQL-запрос и выводит около 1300 строк данных в форматированный файл XML.

Моя первоначальная программа всегда нормально работала в режиме отладки. Однако в режиме выпуска он достигнет примерно 750-го результата SQL, а затем умрет с ошибкой. Ошибка заключалась в том, что определенный столбец данных не мог быть прочитан, даже если метод Read () объекта SqlDataReader только что вернул true.

Эта проблема была устранена путем использования StringBuilder для всех операций в коде, где раньше было «строка1 + строка2». Я не говорю о конкатенации строк внутри цикла запроса SQL, где StringBuilder уже использовался. Я говорю о простых конкатенациях между двумя или тремя короткими строковыми переменными ранее в коде.

У меня создалось впечатление, что C # достаточно умен, чтобы управлять памятью, складывая вместе несколько строк. Я ошибся? Или это указывает на какую-то другую проблему с кодом?


person Bryan Roach    schedule 22.04.2009    source источник
comment
Не могли бы вы вставить фрагмент кода с конкатенацией строк по сравнению с версией StringBuilder? Единственная причина, по которой я мог подумать, что вы можете получить разницу между конкатенацией строк и StringBuilder, заключается в том, что вызывается перегрузка некоторых вещей, но это не должно иметь эффекта, как вы описываете.   -  person Jonathan Rupp    schedule 23.04.2009
comment
Исходный код: строка filepath = path + fileroot + .xml; Обновленный код: string filepath = new StringBuilder (path) .Append (fileroot) .Append (.xml) .ToString (); Я сделал такие изменения в нескольких местах. Все это перед основным циклом. Внутри основного цикла я всегда использовал StringBuilder для создания содержимого XML-файла. (Я не использую XML API, потому что это было быстрее, и это должна быть просто быстрая и грязная программа.)   -  person Bryan Roach    schedule 23.04.2009


Ответы (8)


Помимо того, что вы делаете, вероятно, лучше всего делать с помощью XML API вместо строк или StringBuilder. Я сомневаюсь, что ошибка, которую вы видите, связана с конкатенацией строк. Возможно, переход на StringBuilder просто замаскировал ошибку или изящно ее преодолел, но я сомневаюсь, что использование строк действительно было причиной.

person Joey    schedule 22.04.2009
comment
Спасибо всем за ваши ответы. Я все еще видел ошибки для определенных наборов данных после всех моих изменений. Я думаю, что реальный корень проблемы был в соединении SQL, и это переключение на StringBuilder просто замаскировало ошибку, как сказал Йоханнес. Я решил проблему, используя класс-оболочку SQL из другого проекта. Этот класс преобразует весь набор результатов SQL в объект Dictionary, так что нет необходимости держать набор результатов и соединение SQL открытыми. - person Bryan Roach; 23.04.2009

Чтобы ответить на ваш вопрос: Контатенация строк в C # (и .NET в целом) "безопасна", но выполнение ее в замкнутом цикле, как вы описываете, может вызвать серьезные нагрузка на память и нагрузку на сборщик мусора.

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

Справочная информация. Строки .NET неизменяемы, поэтому при такой конкатенации:

var stringList = new List<string> {"aaa", "bbb", "ccc", "ddd", //... };
string result = String.Empty;
foreach (var s in stringList)
{
    result = result + s;
}

Это примерно эквивалентно следующему:

string result = "";
result = "aaa"
string temp1 = result + "bbb";
result = temp1;
string temp2 = temp1 + "ccc";
result = temp2;
string temp3 = temp2 + "ddd";
result = temp3;
// ...
result = tempN + x;

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

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

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

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

string a = "aaa";
string b = "bbb";
string c = "ccc";
// ...
string temp1 = "aaabbb";
string temp2 = "aaabbbccc";
string temp3 = "aaabbbcccddd";
string temp4 = "aaabbbcccdddeee";
string temp5 = "aaabbbcccdddeeefff";
string temp6 = "aaabbbcccdddeeefffggg";
// ...

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

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

person Daniel Fortunov    schedule 22.04.2009
comment
Я почти уверен, что a + b + c + d ... в одном операторе не генерирует промежуточные строки. Он больше похож на String.Concat. - person Joe; 23.04.2009
comment
Джо: Вы совершенно правы. Я зашел слишком далеко в упрощении ответа. Я добавил цикл, чтобы сделать его более точным. Это лучше? - person Daniel Fortunov; 23.04.2009
comment
Абсолютно. Ключевым моментом является то, что многие повторяющиеся конкатенации (например, в цикле) намного эффективнее с StringBuilder. - person Joe; 24.04.2009

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

Это почти наверняка ошибка в вашем коде.

Возможно, вы связываете очень большое количество строк. А может это что-то совсем другое.

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

person Joe    schedule 22.04.2009

Сколько времени займет версия конкатенации по сравнению с версией построителя строк? Возможно, ваше соединение с БД закрывается. Если вы делаете много конкатенации, я бы пошел с StringBuilder, так как он немного более эффективен.

person Darren Kopp    schedule 22.04.2009

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

Другая возможная причина заключается в том, что длина строки - это int, поэтому максимально возможная длина - Int32.MaxValue или 2 147 483 647.

В любом случае StringBuilder лучше, чем "строка1 + строка2" для этого типа операции. Хотя было бы еще лучше использовать встроенные возможности XML.

person Jeremy    schedule 22.04.2009

string.Concat(string[]) - это, безусловно, самый быстрый способ объединения строк. Он убивает StringBuilder в производительности при использовании в циклах, особенно если вы создаете StringBuilder на каждой итерации. Есть множество ссылок, если вы погуглите "формат строки c # vs построитель строк" или что-то в этом роде. http://www.codeproject.com/KB/cs/StringBuilder_vs_String.aspx дает вам представление о времени. Здесь string.Join выигрывает тест на конкатенацию, но я считаю, что это потому, что string.Concat(string, string) используется вместо перегруженной версии, которая принимает массив. Если вы посмотрите на код MSIL, созданный различными методами, вы увидите, что происходит под капотом.

person Paw Baltzersen    schedule 23.04.2009

Вот мой снимок в темноте ...

Строки в .NET (не построители строк) попадают в внутренний пул строк. В основном это область, управляемая CLR для совместного использования строк для повышения производительности. Здесь должен быть какой-то предел, хотя я понятия не имею, что это за предел. Я полагаю, что все конкатенации, которые вы делаете, достигают потолка внутреннего пула строк. Итак, SQL говорит: «Да, у меня есть для вас ценность, но он не может никуда ее передать, поэтому вы получаете исключение».

Быстрый и простой тест - nGen. свою сборку и посмотрите, появляется ли по-прежнему ошибка. После nGen'ing ваше приложение больше не будет использовать пул.

Если это не удастся, я свяжусь с Microsoft, чтобы попытаться получить некоторые точные подробности. Думаю, моя идея звучит правдоподобно, но я понятия не имею, почему она работает в режиме отладки. Возможно, в режиме отладки строки не интернируются. Я тоже не эксперт.

person Bob    schedule 22.04.2009

При соединении строк я всегда использую StringBuilder. Он разработан для этого и более эффективен, чем простое использование «строка1 + строка2».

person patjbs    schedule 22.04.2009
comment
Плохой совет. string1 + string2 во многих ситуациях быстрее StringBuilder: StringBuilder выигрывает при выполнении большого количества конкатенаций в цикле. - person Joe; 23.04.2009
comment
Спасибо, что дал мне знать. Я читал, что это было быстрее на C # через CLR, но если это не так, полезно знать. - person patjbs; 23.04.2009
comment
Я только что провел несколько тестов, и даже объединение двух коротких строк через str1 + str2 действительно медленнее или, в лучшем случае, не быстрее, чем при использовании StringBuilder. И несколько отличных статей в блогах, которые я с тех пор прочитал, склонны согласиться с этим выводом. Хотя использование str1 + str2 может быть удобным удобством и не оказывает значительного влияния на производительность при объединении небольших строк, StringBuilder, безусловно, не хуже и будет выигрывать по мере роста числа объединений (всего 4, разница составляет sig). - person patjbs; 23.04.2009