Pages

Thursday, May 17, 2007

Use of C# patterns over the old Try Catch model

C# provides multiple patterns that can be used in the place of try catch, the later usually hampers the performance of an application.

say for example, I have an object being returned from a method call.
This object could either be a byte or byte array.
I used to program this way earlier,

byte [] localBytes;
bool returnValueIsByte = false;
try
{
localBytes = (byte [])ObjectReturned;
}
catch
{
try
{
localBytes = new localBytes[1];
localBytes[0] = (byte) ObjectReturned;
returnValueIsByte = true;
}
catch
{
throw new Exception("The object returned is neither a Byte nor a Byte array");
}
}

Instead I learnt coding as below

localBytes = ObjectReturned as byte[];
if(localBytes == null)
{
localBytes = new localBytes[1];
localBytes[0] = (byte) ObjectReturned;
returnValueIsByte = true;
}

The use of "as byte[]" is very useful, you can do the same type for "is a" relationships, say if ObjectReturned is a type of some interface.
An alternative would be to use the below:
Check if the object is of type byte or byte array and then assign it appropriately

if(ObjectReturned.GetType().Equals(typeof(byte)))
{
//code here.
}

2 comments:

Anonymous said...

Good to know that, but how exactly the try catch block is slower?

Ram said...

Hello there,

The below ARTICLE from a C# developer, gives a complete picture of the try-catch model, I just suggested better ways, but this one below gives you a in-depth picture :

There is a small performance hit in setting up a try-catch block, so if you are in a loop you can get a perf gain by wrapping the for loop in a single try-catch instead of using a try-catch inside the loop. However, if the loop must continue to enumerate the items regardless of a single item's thrown exception then you must put the try-catch inside the loop.
There is also a small perf hit in the catch block when the exception is caught because the runtime must examine each catch handler to determine if it is suitable to
handle a given exception. If not handled the exception continues to
propagate up the call stack, but there is still a non-zero hit in making the decision not to handle the exception; small, but not nothing.

Most of the perf hit comes from actually throwing the exception, not just from using a try-catch construct, or just from catching it.
If the inner catch rethrows the exception, then you now have two
exceptions thrown, so the perf hit will be greater, approx twice as much perf hit. The reason is that the majority of the perf hit comes from the mechanics of walking the stack twice, the first time to locate a catch handler, and the second time from running downstream finally blocks before
execution control is handed off the catch handler that deals with the exception. The stack walk on the 1st pass actually transitions through the kernel, makes system calls to determine if a debugger is attached, etc. This
is entirely non-local, so cache misses occur, pages may get faulted into memory, etc. This can happen each time an exception is thrown, so if you do a lot of try-catch-rethrow your performance will degrade, one would expect
in a linear fashion after the system has warmed up.

As a double-caveat, I regard the extra perf hit from rethrowing an exception to be the cost of doing business, and I will rethrow as necessary in order to add sufficient context to make correcting the problem as easy for the end user as possible. This is because once you accept that you are in a non-performant path then the extra cost is negligible compared to the cost
associated with an end user calling support saying "you know that error message that says 'null reference'? Yeah, well, what the heck does that mean?". In other words, my feeling is that clarity is more important then a squeezing a few grams of perf fat out of the code. Of course, the particulars of the code and how it is used dictate the ultimate strategy and
structure of what you must do.

Basically, the strategy I use is to never throw an exception unless there is a problem that cannot be handled, and then use try-catch-wrap-rethrow as often as necessary to provide context as much as is necessary; once started down the exception path I regard the extra perf hit as a minor issue.