
In the .NET Framework, appending an integer as a string to an underlying buffer, such as a StringBuilder or HttpResponse, is very inefficient. This is because the implementation must convert the integer to a string, which is allocated upon the managed heap.
This C# benchmark article shows how you optimize integer appends.
To start, let's look at the benchmark harness itself, which tests the Append method on the StringBuilder type to the custom Digits method. Each StringBuilder is appended to with the numbers 0 - 19999. The program text also shows the Digits extension method: this method receives an integer parameter, and actually extracts each digit from the integer at a time from left to right, and then appends it to the underlying buffer.
Program that optimizes integer appends [C#]
using System;
using System.Diagnostics;
using System.Text;
class Program
{
const int _max = 1000;
static void Main()
{
GC.Collect();
StringBuilder b1 = new StringBuilder();
var s1 = Stopwatch.StartNew();
for (int i = 0; i < _max; i++)
{
for (int a = 0; a < 20000; a++)
{
b1.Append(a);
}
b1.Length = 0;
}
s1.Stop();
GC.Collect();
StringBuilder b2 = new StringBuilder();
var s2 = Stopwatch.StartNew();
for (int i = 0; i < _max; i++)
{
for (int a = 0; a < 20000; a++)
{
b2.Digits(a);
}
b2.Length = 0;
}
s2.Stop();
Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds) / _max).ToString("0.00 ms"));
Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds) / _max).ToString("0.00 ms"));
Console.Read();
}
}
static class Extensions
{
public static void Digits(this StringBuilder builder, int number)
{
if (number >= 100000000)
{
// Use system ToString.
builder.Append(number.ToString());
return;
}
if (number < 0)
{
// Negative.
builder.Append(number.ToString());
return;
}
int copy;
int digit;
if (number >= 10000000)
{
// 8.
copy = number % 100000000;
digit = copy / 10000000;
builder.Append((char)(digit + 48));
}
if (number >= 1000000)
{
// 7.
copy = number % 10000000;
digit = copy / 1000000;
builder.Append((char)(digit + 48));
}
if (number >= 100000)
{
// 6.
copy = number % 1000000;
digit = copy / 100000;
builder.Append((char)(digit + 48));
}
if (number >= 10000)
{
// 5.
copy = number % 100000;
digit = copy / 10000;
builder.Append((char)(digit + 48));
}
if (number >= 1000)
{
// 4.
copy = number % 10000;
digit = copy / 1000;
builder.Append((char)(digit + 48));
}
if (number >= 100)
{
// 3.
copy = number % 1000;
digit = copy / 100;
builder.Append((char)(digit + 48));
}
if (number >= 10)
{
// 2.
copy = number % 100;
digit = copy / 10;
builder.Append((char)(digit + 48));
}
if (number >= 0)
{
// 1.
copy = number % 10;
digit = copy / 1;
builder.Append((char)(digit + 48));
}
}
}
Results
2.97 ms
2.03 msPerformance results. The results are not really dramatic, and they are measured in milliseconds, not nanoseconds. The Digits method, in this case, performed the task nearly one millisecond faster (about one third faster). The reason for this is that the StringBuilder type must allocate the integers in separate objects, while the Digits method does not require any allocations.

There is some mathematics in the Digits method, and they bear explaining. First, the Digits method doesn't handle negative numbers or numbers greater than eight digits. These restrictions could certainly be relaxed in a new implementation. The bulk of the Digits method uses modulo division and regular division to extract digits. The if-statements are chained so that the left-most digits are printed first. As we go along, each digit is converted to a character by adding 48; this converts an integer to its integer character representation in ASCII.

This method may actually be worthwhile using in some cases, such as in an ASP.NET website that must print thousands of numbers to the HttpResponse (you could change the method so it receives an HttpResponse). In most programs, particularly those where performance isn't constrained, it is not worth it. Also, using this method may be "worth it" to you if you are programming a boring application, but imagine you are programming a rocket ship navigation system.

It is possible to optimize the implementation of how integers are appended to an underlying buffer stream in the C# language and .NET Framework. As with other append optimizations, it helps to avoid intermediate allocations and representations.
StringBuilder Secrets