Compare approaches of using ToString on ints. There are performance and functional differences with the NumberFormat on integers. Construct best-practices by inspecting the IL code generated.
What does MSDN say about the ToString method on integers? To answer this I looked at this page at msdn.microsoft.com.
What MSDN says. ToString returns a human-readable string that is culture-sensitive. For example, for an instance of the Double class whose value is zero, the implementation of Double.ToString might return "0.00" or "0,00" depending on the current UI culture.
How does ToString() work? Here I compare ToString() with no parameters (no format string) and ToString() with the NumberFormatInfo specified. ToString() can receive a System.Globalization. NumberFormatInfo parameter.
using System;
using System.Globalization; // Important
class Program
{
static void Main()
{
int a = 4000;
int b = 654;
double c = 453.4;
double d = 50000.55555;
string a1 = a.ToString();
string a2 = a.ToString(NumberFormatInfo.InvariantInfo);
Console.WriteLine(a1 + " " + a2);
// 4000 4000
string b1 = b.ToString();
string b2 = b.ToString(NumberFormatInfo.InvariantInfo);
Console.WriteLine(b1 + " " + b2);
// 654 654
string c1 = c.ToString();
string c2 = c.ToString(NumberFormatInfo.InvariantInfo);
Console.WriteLine(c1 + " " + c2);
// 453.4 453.4
string d1 = d.ToString();
string d2 = d.ToString(NumberFormatInfo.InvariantInfo);
Console.WriteLine(d1 + " " + d2);
// 50000.55555 50000.55555
}
}Identical results. What we see above is that the ToString() without a parameter (a1, b1, c1, d1) all return the same strings as the overloaded methods with a parameter.
Invariant cultures. What the word invariant means is that the format doesn't vary with the social culture of the machine.
Mine isn't. I use ToString() in many places simply to convert an integer to a string (5 to "5", for example). What I found when inspecting the intermediate language is that ToString() with no parameters actually gets the culture internally, but this is non-optimal.
It uses a property called CultureInfo to get the current culture. It then sends the System.IFormatProvider to the ToString() method. So ToString() with no parameters simply gets NumberFormatInfo on its own.
.method public hidebysig instance class System.Text.StringBuilder
Append(int32 'value') cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: ldarga.s 'value'
L_0003: call class System.Globalization.CultureInfo
System.Globalization.CultureInfo::get_CurrentCulture()
L_0008: call instance string System.Int32::ToString(class System.IFormatProvider)
L_000d: call instance class System.Text.StringBuilder
System.Text.StringBuilder::Append(string)
L_0012: ret
}In my research, I have found that property accesses like get_CurrentCulture() above are slow. So, my solution to this inefficiency is to pass ToString() an already-created NumberFormatInfo. Here's how I changed the code.
// // A. // This code converts the int 900 to a string. // string a = 900.ToString(); // "900" // // B. // This code converts the int 900 to a string. // Has the same exact results. // NumberFormatInfo n = CultureInfo.InvariantCulture.NumberFormat; string b = 900.ToString(n); // "900"
B is different because it eliminates the need for ToString() to access a property internally. This is a significant optimization for this operation, particularly in tight loops.
Somewhere around 10% for invariant cultures. This can build on my work with StringBuilder mistakes, Split improvements, Response.Write performance, and others.
using System;
using System.Globalization; // important
class Program
{
static void Main()
{
int m = 10000000;
//
// Run ToString() benchmark
//
long t1 = Environment.TickCount;
for (int i = 0; i < m; i++)
{
string a = i.ToString();
}
long t2 = Environment.TickCount;
//
// Run ToString(NumberFormatInfo) benchmark
//
NumberFormatInfo f = CultureInfo.InvariantCulture.NumberFormat;
for (int i = 0; i < m; i++)
{
string a = i.ToString(f);
}
long t3 = Environment.TickCount;
Console.WriteLine((t2 - t1));
Console.WriteLine((t3 - t2));
Console.Read();
}
}Specifying the NumberFormatInfo on int.Parse() also improves performance. int.Parse uses the same overload logic that int.ToString() does. Here's what you can use for int.Parse().
//
// Use NumberFormatInfo with int.Parse
//
NumberFormatInfo f = CultureInfo.InvariantCulture.NumberFormat;
int i = int.Parse("2000", f); // 2000When using simple integers or other value types, specify the invariant culture number format. This improves the relevant execution time by 10%.
| Slow version | Fast version |
| string a = 5.ToString(); | NumberFormatInfo n = CultureInfo.InvariantCulture.NumberFormat; string a = 5.ToString(n); |
| int i = "5".ToString(); | NumberFormatInfo n = CultureInfo.InvariantCulture.NumberFormat; int i = "5".ToString(n); |
Inspecting intermediate language in Reflector (now maintained by Red Gate) is an excellent way to develop best practices and rules for optimization. Here I show a clear way to optimize int.Parse and int.ToString() methods without developing custom algorithms.