Конкатенация строк небезопасна в C #, нужно использовать StringBuilder? - PullRequest
5 голосов
/ 23 апреля 2009

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

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

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

Эта проблема была исправлена ​​с помощью StringBuilder для всех операций в коде, где ранее было "string1 + string2". Я не говорю о конкатенации строк в цикле запросов SQL, где уже используется StringBuilder. Я говорю о простых конкатенациях между двумя или тремя короткими строковыми переменными ранее в коде.

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

Ответы [ 8 ]

15 голосов
/ 23 апреля 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 использует изменяемый буфер для уменьшения количества выделений, необходимых для построения твоя строка.

11 голосов
/ 23 апреля 2009

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

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

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

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

7 голосов
/ 23 апреля 2009

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

3 голосов
/ 23 апреля 2009

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

1 голос
/ 23 апреля 2009

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

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

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

0 голосов
/ 23 апреля 2009

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

0 голосов
/ 23 апреля 2009

Вот мой выстрел в темноте ...

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

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

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

0 голосов
/ 23 апреля 2009

При объединении строк я всегда использую StringBuilder. Он разработан для этого и более эффективен, чем просто использование "string1 + string2".

...