trycatch in .NET 2.0 - Part 2
As I pointed out in a previous entry, try{}catch{} works differently in .NET 2.0 as it does in .NET 1.x. In 2.0, non-exception "throwables" are wrapped in a RuntimeWrappedException which can be caught by try{}catch(Exception ex){}, while, to catch then in .NET 1.x, you were required to use try{}catch{}. This led me to wonder 1) how does this work? and 2) is there even a purpose for try{}catch{} anymore?
So I started my research by... well, passing the buck, or going to the source, depending on your view on things. The first thing I did was ask Brad Abrams if he had some insights about this. He then forwarded me to Jonathan Keljo who I guess then forwarded me to Joe Duffy...sheesh what a line up! The best of the best of the best right there! Goodness, I feel like an ant...
Anyhow... here's the thing. Joe affirmed that there are at least two purposes for try{}catch{} in .NET 2.0
Here are his exact words...
(1) If your assembly doesn't have "wrapping" turned on;
(2) If you don't care to access the exception information via a variable. This can help to reduce the temptation to accidentally do a catch(Exception e){ /*...*/; throw e; } when you meant to do catch { /*...*/ throw; }.
OK cool, now what about the mechanics? Joe explains that the wrapping of RuntimeWrapperException around non-exception throwables is due to the fact that C# and VB auto opt-in all assemblies to what they call "the wrapping plan". This means there is a RuntimeCompatibilityAttribute(...) on the assembly where, in this case, WrapNonExceptionThrows=true is set. When this is the case all non-System.Exception "exceptions" get wrapped into a RuntimeWrappedException, which, as Joe points out of course does inherit from System.Exception. Conversely, if WrapNonExceptionThrows=false is set, then there is no magical wrapping.
Let's see this all in action. But before we do, last time I wrote about this I forgot to what mention happens when you compile the below code (this is the same code from the previous blog entry about this topic)
// ThrowerHarness.cs namespace ThrowerExample { class ThrowerHarness { static void Main(string[] args) { try { Thrower.ThrowException( ); } catch (System.Exception ex) { System.Console.WriteLine("System.Exception error: " + ex.Message); } catch { System.Console.WriteLine("Non System.Exception based error."); } try { Thrower.ThrowString( ); } catch (System.Exception ex) { System.Console.WriteLine("System.Exception error: " + ex.Message); } catch { System.Console.WriteLine("Non System.Exception based error."); } } } }
Compiling this code actually gives the following warnings...
throwerlib.cs(16,7): warning CS1058: A previous catch clause already catches all exceptions. All non-exceptions thrown will be wrapped in a System.Runtime.CompilerServices.RuntimeWrappedException throwerlib.cs(29,7): warning CS1058: A previous catch clause already catches all exceptions. All non-exceptions thrown will be wrapped in a System.Runtime.CompilerServices.RuntimeWrappedException
As you can see, we know at compile that try{}catch(Exception ex){} will grab the exceptions.
Here's the IL we are using, which contains the Thrower class and is referenced by the C# application.
// ThrowerLib.il .assembly ThrowerLib { } .class public Thrower { .method static public void ThrowException( ) { ldstr "ThrowException exception from the IL world!" newobj instance void [mscorlib]System.Exception::.ctor(string) throw ret } .method static public void ThrowString( ) { ldstr "Weird exception!" throw ret } }
Of course, there's our command syntax...
ilasm /t:dll ThrowerLib.il csc Thrower.cs /r:ThrowerLib.dll
After a successful assembly and compilation, running this application gives the following output...
System.Exception error: ThrowException exception from the IL world! System.Exception error: An object that does not derive from System.Exception has been wrapped in a RuntimeWrappedException.
Now, what about what Joe was saying about the RuntimeCompatibilityAttribute? Well, if we were to tack the following onto our existing class, we would get absolutely no warnings. Pretty cool...
using System.Runtime.CompilerServices; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = false)]
Not only that, but here's the output as we would expect.
System.Exception error: ThrowException exception from the IL world! Non System.Exception based error.
As you can see it did not wrap the exception. So, now, the picture of how things work and how to modify them is a bit more complete.
You can download the code for this entry by the link below.
Joe Duffy also has an explanation of this, although his is from the Beta 2 era (I only see one change -- WrapNonClsExceptions -> WrapNonExceptionThrows; but there may be more). Here is his article.