In my entry on “native” languages on the CLR, I made a blanket statement that the intrinsic VB conversion operators would be faster than the conversion functions in the Convert class because they compile down to IL rather than function calls. In my quest to emphasize my point, however, I made a serious misstatement which was not intentional but should be corrected. Not all intrisic operators compile down to simple IL instructions, although most do so. And the ones that don’t are worth discussing. So let me spend a moment talking about conversions.
The intrinsic conversion operators (CObj, CByte, CShort, CInt, CLng, CDec, CSng, CDbl, CDate, CStr, CChar, CType, DirectCast) generally fall into several broad categories of implementation:
- Conversions that are pure IL statements. The best example here is the conversion from Integer to Long or Long to Integer. This compiles directly down to a conv.i8 or conv.i4.ovf instruction, respectively. You really, really, really don’t want to be calling functions to do this kind of conversion unless you’ve got a lot of faith in inliners, which I generally don’t.
- Conversions that are mostly pure IL statements. The best example here is the conversion from Double to Integer. This compiles down to a call to Math.Round and then a conv.i4.ovf instruction. The purpose of the extra call is to implement banker’s rounding, which the CLR doesn’t natively support. (In banker’s rounding, a decimal number equidistant between two whole numbers is rounded to the nearest even number. So 3.5 rounds to 4, but 2.5 rounds to 2.) In this case, calling Convert.ToInt32(Double) is not the same as CInt(Double), and you have to choose which semantic you desire.
- Conversions that are calls to the Convert class. The Decimal type is not natively supported by the CLR. As such, conversions to and from Decimal (with the exceptions listed below) turn into calls to Convert.ToDecimal() or the appropriate helper. There’s no point in us reimplementing the conversion since it’s the same for both the CLR and VB. In this case, calling Convert.ToDecimal(Integer) is exactly the same as CDec(Integer).
- Conversions that call VB specific helpers. There are only two cases that fall into this category: conversions to and from String, and conversions from Object.
- Conversions to and from String call VB specific helpers because VB does a variety of extra things for you. For example, when converting String to Integer, we’ll parse decimal numbers and do the appropriate rounding. So while Convert.ToInt32(“10.5“) will barf with an exception, CInt(“10.5“) will work just fine. And this is just one example of the nice stuff that we’ll do for you. As always, though, extra functionality can equal extra cost. So if you just want the very basic functionality that Convert.ToInt32() calls (or you prefer calling two functions), you might get slightly better performance by calling it instead of using CInt on a String.
- Conversions from Object call VB specific helpers because VB needs to be able to implement VB specific semantics (such as the String conversions and banker’s rounding) when converting values from one type to another. For example, Convert.ToInt32(CObj(“10.5“)) still throws an exception, while CInt(CObj(“10.5“)) won’t.
One interesting thing to note is that if you really want only the CLR conversions and nothing else, the DirectCast operator will turn off most of the VB-specific conversion features (I believe we still do banker’s rounding, though). Then you can call the Convert class methods when you run into conversions that DirectCast doesn’t support. I still think that this is an extreme way to go about things, but I know some people are just that way. Me, I use the regular VB operators and then look out for performance sensitive areas where I might need something different.