Exception filters and C#

We shipped our April 2004 issue of MSDN Magazine this week, and I've received a few reader's emails about something I wrote in the “.NET Matters” column.

I was answering a question concerning exception filters and C#.  While there are two kinds of exception filters, type-filters and user-filters, when people say exception filters they're almost always referring to user-filters.  Managed C++, Visual Basic .NET, and MSIL all support both type-filters and user-filters.  C# only supports the former.

A few people have commented that I was negative towards C# in my writings.  On the contrary, I'm a huge fan of C# and use it for almost all of my managed coding.  But I think it's important for developers to realize that languages have strengths and languages have weaknesses.  No language is better than all others for all tasks.  For example, while I use C# for almost all of the managed code I write, if I'm writing an application that needs to work with the Office SDK, I'm going to use Visual Basic .NET 90% of the time; it's just cleaner in many situations.  If' I'm going to be doing a minimal amount of interop with Win32, I'll probably stick with C#, but any amount more than that and it's C++ for me; it's a lot less code.

Back to exception filters...

Exception filters allow you to specify a conditional clause for each catch block.  Instead of a catch block handling all exceptions of a specific type, they allow us to write catch blocks that handle exceptions of a specific type (or all exceptions) only when a certain condition is true.  For example, consider the following Visual Basic .NET code:

Try
' do something that could throw an exception
Catch e As FileNotFoundException When MyFilterMethod(e)
Console.WriteLine(“first“)
Catch e As FileNotFoundException When MyFilterMethod2(e)
Console.WriteLine(“second“)
Catch e As FileNotFoundException When MyFilterMethod3(e)
Console.WriteLine(“third“)
Catch e As DivideByZeroException
Console.WriteLine(“fourth“)
End Try

“first” is only going to be written to the console if the try block throws a FileNotFoundException and if the MyFilterMethod returns true when passed the exception.  “second” is only going to be written to the console if the try block throws a FileNotFoundException and if the MyFilterMethod2 returns true when pass the exception. etc.  If you take a look at the MSIL generated when this is compiled, you'll see a section of code that looks something like:

IL_0015: isinst [mscorlib]System.IO.FileNotFoundException
IL_001a: dup
IL_001b: brtrue.s IL_0020
IL_001d: pop
IL_001e: br.s IL_002f
IL_0020: dup
IL_0021: stloc.2
IL_0022: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception)
IL_0027: ldloc.2
IL_0028: call bool Test::MyFilterMethod(class [mscorlib]System.Exception)
IL_002d: brtrue.s IL_0032
IL_002f: ldc.i4.0
IL_0030: br.s IL_0033
IL_0032: ldc.i4.1
IL_0033: endfilter
...
.try IL_0000 to IL_0015 filter IL_0015 handler IL_0035 to IL_003d

The .try line is describing the filter used for the corresponding handler.  If you were to decompile this (which you might have trouble doing with a tool since both .NET Reflector and Anakrino have trouble parsing exception filters), you'd see that the filter is actually being evaluated as a single expression, something like:

Catch When (IsInst(exc, FileNotFoundException) && MyFilterMethod(exc))

This is because the CLR expects a single expression rather than first checking the type and then checking the filter.  We can get functionality in C# similar to that shown above in VB (the important word here is “similar”) in one of a few ways.  One way is as follows:

try
{
// do something that could throw an exception
}
catch(Exception e)
{
if (e is FileNotFoundException && MyFilterMethod(e))
{
// ...
}
else if (e is FileNotFoundException && MyFilterMethod2(e))
{
// ...
}
else if (e is FileNotFoundException && MyFilterMethod3(e))
{
// ...
}
else if (e is DivideByZeroException)
{
}
else throw;
}

This is somewhat how filters are analyzed by the CLR.  An exception is thrown, we catch it, and we walk through each of our filters seeing if it matches any of them.  If it doesn't, it's rethrown.  Note that there are a few problems with this approach.  We have to cast the Exception exc to the correct type in order to use properties from the derived type.  We can change the above code to the following to solve that problem:

try
{
// do something that could throw an exception
}
catch (FileNotFoundException e)
{
if (MyFilterMethod(e))
{
// ...
}
else if (MyFilterMethod2(e))
{
// ...
}
else if (MyFilterMethod3(e))
{
// ...
}
else throw;
}
catch (DivideByZeroException e)
{
//...
}

In addition to the previous example, this gives us an exception variable already of the correct type.  Many people I've talked to prefer the former snippet, while others prefer the latter.  It's really a matter of personal preference.  The point here is that there's always more than one way to express a concept and almost invariably there's some advantage to each approach.

Now, remember that earlier in this post and in the magazine I wrote the key point to remember is that these are only similar to exception filters and can't exactly reproduce them.  As Chris Brumme writes in his blog (unrelated sections omitted),

Gee, my language has rethrow, but no filter. Why can’t I just treat the following constructs as equivalent?

try {…} filter (expression) catch (Exception e) {…}
try {…} catch (Exception e) { if (!expression) rethrow; …}

In fact, ‘rethrow’ tries hard to create the illusion that the initial exception handling is still in progress. It uses the same exception object. And it augments the stack trace associated with that exception object, so that it includes the portion of stack from the rethrow to the eventual catch.
...
One way that a rethrow differs from a filter is with respect to resumable or restartable exceptions. We’ve already seen how SEH allows an exception filter to return EXCEPTION_CONTINUE_EXECUTION. This causes the faulting instruction to be restarted. Obviously it’s unproductive to do this unless the filter has first taken care of the faulting situation somehow. It could do this by changing the register state in the exception context so that a different value is dereferenced, or so that execution resumes at a different instruction. Or it could have
modified the environment the program is running in, as with the VirtualProtect cases that I mentioned earlier. In V1 and V1.1, the managed exception model does not support restartable exceptions.
...
Probably the biggest impact from terminating the first pass of exception handling early, as with the examples above, is on debugging. Have you ever attached a debugger to a process that has failed with an unhandled exception? When everything goes perfectly, the debugger pops up sitting in the context of the RaiseException or trap condition. That’s so much better than attaching the debugger and ending up on a ‘rethrow’ statement. What you really care about is the state of the process when the initial exception was thrown. But the first pass has terminated and the original state of the world may have been lost. It’s clear why this happens, based on the two pass nature of exception handling.

I hope that clears things up a bit.

For those of you who subscribe to the magazine, thanks!  I hope you it and I hope you enjoy the column.  Next month's will soon be on its way to the presses.  While I enjoyed writing the column for April, I personally had a lot of fun writing the column for the May issue... I hope you have as much fun reading it... stay tuned...

For those of you who don't subscribe, why not? ;)  Seriously, though, we take pride in our content and want to get it out to the world to help all developers using Microsoft tools and technologies.  After giving our subscribers some time to read through the magazine and to digest the information, we put all of our content online for free!  In fact, as I noted in a post before my abysmally long hiatus from blogging, not only is it available online, it's also available in a downloadable .chm file.  For example, our February 2004 issue, featuring content all about SQL Server Yukon, is available here https://msdn.microsoft.com/msdnmag/issues/04/02/default.aspx for online viewing, but you can also get it in downloadable form here https://msdn.microsoft.com/msdnmag/issues/04/02/MSDNMag0402.chm.  Download it, take it on the train with you, whatever.  We hope you find it helpful.