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

StackOverflow https://stackoverflow.com/questions/779051

Вопрос

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

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

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

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

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

Это было полезно?

Решение

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

Другие советы

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

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

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

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

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

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

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

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

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

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, сгенерированный различными методами, вы увидите, что происходит под капотом.

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top