In this post, I would like to share some things about Conversion styles in .NET I learned a few days back. Basically in this case, I am talking about casting any numeric value to an equivalent char
type.
There are three ways one can convert one type to another, in this case numeric to char:
- Direct Casting
- Via
Convert
helper class - Via
IConvertible
interface
Let's dig a bit deeper into each of these styles and see its pros and cons if any.
Direct Casting
This conversion style is the most basic and most efficient one. In this style, the compiler need not emit any special OpCodes or method calls. Instead, it just emits basic OpCodes which does the job efficiently. Basically compiler emits conv.u2 or conv.ovf.u2 OpCode instructions for unchecked and checked operations respectively.
Let's see the IL for this operation:
int value = 99;
char c = (char)value;
For the above code, the IL generated is:
IL_0000: nop
IL_0001: ldc.i4.s 99
IL_0003: stloc.0
IL_0004: nop
IL_0005: ldloc.0
IL_0006: conv.u2
IL_0007: stloc.1
IL_0008: nop
IL_0009: ret
As you can see, there is no extra complex OpCodes being generated nor any method calls. Only opcode which does the actual conversion is generated at IL_0006
line.
Although this style is the most efficient one, the only drawback of using this style is that it does not support culture specific conversion. In other words, you can’t specify IFormatProvider
to the conversion operation.
Convert Helper class
In this style, we use Convert
class which is a helper class provided by .NET framework class library (FCL). As you may already know, Convert
class is a static
class which provides a lot of static
methods for conversion operations. Since there is a method call in this scenario, so this is considered to be the second most efficient style among others. The Convert
methods internally checks for overflow operation conditions by default. Hence whenever there is overflow occurrence, it throws an exception of type OverFlowException
.
So in this case, I have used Convert.ToChar(Int32)
API. So the code in FCL for this API looks like below:
public static char ToChar(int value)
{
if (value < 0 || value > 65535)
{
throw new OverflowException(
Environment.GetResourceString("Overflow_Char"));
}
return (char)value;
}
One important point worth remembering here is that, one needs to be careful while using the API Convert.ToChar(Object, IFormatProvider)
with valuetypes
. Because internally, it uses IConvertible
interface for conversion which results in low performance issues as explained in the next section.
IConvertible Interface
In BCL, many basic types viz, Int32
, DateTime
, Char
, etc. do implement interface IConvertible
. Many other types might as well implement this interface, but it’s of no interest here. One important thing to remember here is that this style of conversion is very poor in performance, since in case of valuetypes
, the input is first boxed and then conversion is done on it.
Let's see a sample example:
char c = ((IConvertible)95).ToChar(null);
In the above line, Explicit conversion to IConvertible
is required, because many types in BCL viz, Char
, String
, Int32
, etc. do implement IConvertible
methods explicitly. Looking at the IL for the above code shows the exact problem:
IL_0000: nop
IL_0001: ldc.i4.s 95
IL_0003: box [mscorlib]System.Int32
IL_0008: ldnull
IL_0009: callvirt instance char [mscorlib]System.IConvertible::ToChar
(class [mscorlib]System.IFormatProvider)
IL_000e: stloc.0
IL_000f: ret
As you can see in the IL_0003
instruction, the value is getting boxed. Hence this style of conversion is least efficient of all others. So it's better to avoid this style.
Now let's proceed to do some performance analysis on all three styles, although from the theoretical aspect from the above lines, we know their performance costs but still let's prove it with an example to convince fully. Hence I have used the below code:
static Stopwatch sp = new Stopwatch();
static int counter = 10000000;
static int valueToConvert = 70;
public static void Main()
{
GC.WaitForPendingFinalizers();
GC.Collect();
for (int j = 0; j < 3; j++)
{
Console.WriteLine("Casting style...");
StartTimer();
for (int i = 0; i < counter; i++)
{
Char c = (char)valueToConvert;
}
StopTimer();
PrintElapsedTime();
Console.WriteLine("Using Convert helper method..");
StartTimer();
for (int i = 0; i < counter; i++)
{
Char c = Convert.ToChar(valueToConvert);
}
StopTimer();
PrintElapsedTime();
Console.WriteLine("Via IConvertible interface...");
StartTimer();
for (int i = 0; i < counter; i++)
{
Char c = ((IConvertible)valueToConvert).ToChar(null);
}
StopTimer();
PrintElapsedTime();
Console.WriteLine("\n\n");
}
Console.ReadLine();
}
As you can see from the above test code, I am taking 3 samples of the performance results just to make sure output is quite accurate. Hence from this, I got the below result:
Casting style…
Time elapsed in ms = 22
Using Convert helper method..
Time elapsed in ms = 60
Via IConvertible interface…
Time elapsed in ms = 233
Casting style…
Time elapsed in ms = 255
Using Convert helper method..
Time elapsed in ms = 294
Via IConvertible interface…
Time elapsed in ms = 417
Casting style…
Time elapsed in ms = 439
Using Convert helper method..
Time elapsed in ms = 478
Via IConvertible interface…
Time elapsed in ms = 598
From the above results, it is very much clear that the performance issues are much better with the direct casting and acceptable with Convert
helper methods in this case.
Hope it helps.
Thanks for reading. Your votes/comments are much appreciated.
Happy coding,
Thanks, Zen
Filed under:
C#,
CodeProject,
Dotnet Tagged:
.NET,
blog,
blogger,
C#,
codeproject,
Dotnet,
tips