CodingWorse with a try{}catch{} solution?

I used to teach in a technology program at ODU (until the university sold the program . . . but that’s another story); I still get questions emailed from students about the “right” way to program things etc.  The latest question was about the lack of an “IsNumeric” function in C#; as you probably know, VB.Net has an IsNumeric() function that returns a boolean for if a number is numeric or not.  While VB.Net’s IsNumeric() function is hardly rocket science, it is a very useful function for testing your strings.

Anyway, C# is a little more “roll-up-your-sleeves” and solve your own problem, so there is no equivalent IsNumeric function in C#.  This former student of mine asked if the following was an acceptable alternative:

public static bool isNumeric( string strText )
{
try
{
int i = int.Parse( strText ) ;
return true ;
}
catch
{
return false ;
}
}

What’s wrong with this picture?  This is classic lazy-programming (or, in this case, just inexperienced programming) because the person knows enough about C# to use try{}catch{}, but is using it inappropriately.  Exceptions are for exceptional situations — not for avoiding some real thinking through the issue.  Catching exceptions is hard work on your programs and performance will really suffer: this try{}catch{} isNumeric() function is over 2,000 times slower than this alternative:

public static bool isNumeric( string strText )
{
char[] chars = strText.ToCharArray() ;
for ( int i = 0; i < chars.Length; i++ )
{
if( !Char.IsDigit( chars[ i ] ) ) //could also explore IsNumber
{
return false ;
}
}
return true ;
}

The good ol’ Char class has a baked in IsDigit() function that will also handle localized definitions of numbers; this version of isNumeric() will work with Cyrillic defined numbers as well as our “conventional” numerals — assuming the system is setup for the appropriate region/locale.  This function requires a bit more typing, but iterating over every character in a string and testing for that character’s “IsDigit-ness” is 2,000 times faster than the try{}catch{} solution.  Of course, your mileage may vary based on the length of the string you’re comparing etc (I used strings of 10 characters in my tests).

If blinding perf is what you’re after, this is the alternative isNumeric function you want:

public static bool isNumeric( string strText )
{
char[] chars = strText.ToCharArray() ;
for ( int i = 0; i < chars.Length; i++ )
{
if ( chars[ i ] > 57 || chars[ i ] < 48 )
{
return false ;
}
}
return true ;
}

You lose the universal comparison logic built into Char.IsDigit(), but this is over twice as fast as the previous alternative (and fully 5000+ times faster than the original try{}catch{} function).  Exact statistics are at the bottom of this post.  If your app will only run on machines where characters 48-57 will be numeric — like with our western number system — this is the moet et chandon of the IsNumeric() world.

Yes, you can call into the VB.Net library to get to that IsNumeric() function; this will be slow, too.  Besides, I know some C# die-hards that would rather eat their own young than call a VB.Net library intentionally.  I’ve also omitted a Regex isNumeric() alternative and a few others, but I didn’t set out to write the definitive study on isNumeric for C#.  Originally, I wanted to demonstrate that the “quick-and-dirty” try{}catch{} coding solution to this problem is for those who want to CodeWorse — and since my blog is now on www.CodeBetter.com I figured I better get with the program!

So, using perf monitoring like in this post and a little ILDasm:

• The try{}catch{} CodeWorse approach took 43 seconds to run through 30,000 test calls to IsNumeric where 66% of the strings were not numeric (meaning, 66% return false).  That’s a lot of exceptions!  The IL for the function has 2 calls into MSCorLib and the perf-crushing IL try-catch handlers.

• The Char.IsDigit approach took only .0171 seconds to run through the same 30,000 test calls.  This approach still has 2 calls into MSCorlib in the IL, but none of the try-catch handlers to cause pain and perf agony.

• The simple character comparison array loop was the fastest for these 30,000 calls at .0078 seconds.  Only 1 MSCorLib call.  No try-catch nonsense.

Can you hear me in the back?  The time it takes the try{}catch{} “solution” to work in a loop situation can be measured in whole seconds (perhaps minutes!) while the other approaches only take fractions of fractions of seconds. I can literally type the entire “verbose” character comparison array loop solution in Visual Studio in the time it takes the try{}catch{} “solution” to run — so don’t give me any “quick and dirty is fine” stuff here.  It’s just inexperience or laziness that would have you CodingWorse with try{}catch{} blocks.

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

18 Responses to CodingWorse with a try{}catch{} solution?

1. Mel Grubb says:

Double.TryParse FTW!

2. Eddie says:

Hi, first: Sorry for my bad english – it’s a bit rusty. Very good and informative post! Thank you.
I’m very new to C# and just started/finished reading books/online articles about exception handling with try catch. Well, if you say not to use try catch you mean this “not to use this combo inside loops/methods for something like isNummeric” – right?

But in case for Exceptions (example wrong format catching ReadLine) … so what to do? John Sharp MS Visual C# Step by Step Page 100 says “Exceptions are the object-oriented way of error handling”. I’m a little confused.

Greetings from Germany.
Eddie

3. David says:

Try this for your Regex text.

@”^\-?\d+\.?\d*\$”

-5.0 is ok, but -5. will return false. I don’t use numbers that start with a plus sign, so I left that out.

4. Dave says:

the definition of Regex.Match is as follows, from the docs:
Searches the input string for *an occurrence* of the regular expression supplied in a pattern parameter with matching options supplied in an options parameter.

5. Dave says:

it returned true because it found a match within the string you supplied.

6. sarcoptopus says:

I tried:

string text = “3iow[qdlp389384358″;
Regex _isNumeric = new Regex(@”[+|-]{0,1}[\d]{1,}(\.\d){0,1}[\d]{0,}”);
return _isNumeric.Match(text).Success ;

It returned true.

7. Doug McClean says:

One critical difference between your code and the try{int.Parse}catch{} solution is that int.Parse is culture-sensitive. This is even more important for the proposed double.Parse solution. Thankfully in 2.0 we will have the TryParse methods and won’t have to worry about this stuff.

Also the performance difference depends heavily on how often the string does not parse as an integer, doesn’t it?

8. This post just came in hella wicked kickass handy.

9. ben says:

I wonder if using a regular expression would be even faster? I think the c# regular expression evaluator is pretty damn fast. I found this regular expression over at our former home

[+|-]{0,1}[\d]{1,}(\.\d){0,1}[\d]{0,}

http://dotnetjunkies.com/WebLog/removable%20thoughts/archive/2004/02/19/7567.aspx

10. John Wood says:

I know your blog wasn’t a definitive study of the lack of IsNumeric in C#… but I would like to point out that it just goes under a different name.

double.TryParse

11. Grant says:

Josh — glad to hear you valued Devscovery (http://www.Devscovery.com). It’s tough to beat the face-to-face interaction with some of the best in the business. I’m not sure about TechEd this year . . . the budget gods may require that I pass on Orlando, but I’m exploring my options.

12. Grant says:

Robert — excellent improvement! And the performance gains over the CharArray are just as you suggest. Keep that insane optimizer guy around . . .

13. Robert Jeppesen says:

That little insane optimizer guy inside of me forced me to do!

public static bool isNumeric2( string strText )

{

for ( int i = 0; i < strText.Length; i++ )

{

if ( strText[ i ] > 57 || strText[ i ] < 48 )

{

return false ;

}

}

return true ;

}

This will be nearly 8 times as fast as your fastest version (in release compile, only twice as fast in debug). I just ditched the ToCharArray, since you have direct access to chars via the indexer. There’s no need to create all those arrays.

14. Josh says:

I’ve been silently watching your blog all along, and silently updating my blog… so silent you can’t even see the changes ;).

I’m hoping to do TechED again this year, but we’re in the midst of a re-org, so I’ll have a new bunch of execs to try and butter up. My first TechEd was in Orlando and I had a blast. You planning to be there?

By the way, I attended Devscovery here in Austin a couple weeks ago after your high recommendation. It was well worth the time/money. Richter renewed my interest (crusade?) in proper exception handling, which is why I couldn’t resist adding the part about catching FormatException. It’s a battle I fight often with internal developers. Everyone is too used to the "On error resume next" methodology.

Heck, why do I need my own blog, when I post more content in yours?

15. Grant says:

Great to hear from you again Josh! Are you doing TechEd again this year?

You’re 100% correct that the isNumeric() I conclude with is not identical to the VB.Net IsNumeric() — the VB funtion does a lot with formats etc. Good point!

16. Josh says:

Great general advice about inappriate use of try/catch.

However, I have to disagree that you have produced an equivalent IsNumeric (I’ve gone down this same path of course).

The VB IsNumeric() will say that these strings are numeric:

43.2

12,008

2E12

\$1.99

The closest I’ve found in the .NET Framework is a call to Double.TryParse() (coming in .NET 2.0 we will get Int32.TryParse()). You could wrap that in an IsNumeric() method to get a closer approximation of the VB function.

Of course, your solution is much cleaner and faster, IF your program will only allow digits in a number. If you are trying to validate user input, this may be too much of a restriction.

(PS: As a side note, tell your former student they should have been catching FormatException instead of all exceptions)

17. Travis says:

Super informative post…

/subscribed

18. Really great post. I’ll definitely add this to my Common.Util.StringUtils namespace! Thanks!