Опубликовано
- 1 мин чтения
StringBuilder: избегайте ToString при использовании Append

Это новая часть серии, в которой я разбираю правила качества кода .NET с точки зрения производительности. Сегодня поговорим про CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder.
Описание правила
Правило рекомендует не преобразовывать значения в строку при вызове методов Append или Insert.
var i = 123;
var sb = new StringBuilder();
sb.Append(i.ToString()); // CA1830
Вместо этого лучше передать значение напрямую:
var i = 123;
var sb = new StringBuilder();
sb.Append(i);
Это правило касается примитивных типов вроде byte
, short
, int
, double
, long
и других.
Анализ производительности
Я написал простой бенчмарк, чтобы оценить, насколько эти подходы отличаются по производительности. Результаты в репозитории и на диаграмме ниже.

График сравнения производительности
Разница по времени — небольшая: около 5 микросекунд на моём ноутбуке. Разница по памяти более заметная: около 20 КиБ.
Как работает эта оптимизация?
Когда вы используете метод Append(int), под капотом StringBuilder вызываются методы AppendSpanFormattable<T>
и InsertSpanFormattable<T>
.
Например, упрощённая версия AppendSpanFormattable<T>
выглядит так:
private StringBuilder AppendSpanFormattable<T>(T value) where T : ISpanFormattable
{
if (value.TryFormat(RemainingCurrentChunk, out int charsWritten, format: default, provider: null))
{
m_ChunkLength += charsWritten;
return this;
}
return Append(value.ToString());
}
Этот метод использует интерфейс ISpanFormattable.TryFormat
, чтобы записать значение напрямую во внутренний буфер StringBuilder
как Span<char>
. Это позволяет: избежать аллокации строки; упаковки (boxing) и вызова виртуального метода ToString()
.
Стоит ли заморачиваться?
Скорее да. Даже если не брать в расчёт прирост производительности, такой код:
sb.Append(i);
и выглядит проще и чище, чем:
sb.Append(i.ToString());
И в качестве бонуса, он работает быстрее и потребляет меньше памяти. Win-win.