Exception filters

Topics: C# Language Design
Apr 5, 2014 at 1:38 PM
Edited Apr 5, 2014 at 1:38 PM
In the documentation stands:
Exception filters are preferable to catching and rethrowing because they leave the stack unharmed. If the exception later causes the stack to be dumped, you can see where it originally came from, rather than just the last place it was rethrown.
Isn't this the difference between 'throw' and 'throw e'?
try { … }
catch (MyException e) if (myfilter(e))  //untouched stack trace, C# 6
{
    …
}
While
try { … }
catch (MyException e) 
{
    if (myfilter(e))
      throw e;   //overridden stack trace
   
    …
}
But
try { … }
catch (MyException e) 
{
    if (myfilter(e))
      throw;   //untouched stack trace, C# 1?
   
    …
}
http://stackoverflow.com/questions/881473/why-catch-and-rethrow-exception-in-c
Apr 5, 2014 at 3:18 PM
No; the documentation refers to the actual callstack, while you’re thinking of the StackTrace property in the Exception object.

Imagine you have an unhandled exception. In such a case, the debugger tells you where the exception occurred.

Now imagine you have a catch clause somewhere with a throw; statement in it. Even though the Exception.StackTrace property tells you the whole stacktrace, the debugger now shows you the location of the throw; statement, not the location of the original exception.

The purpose of the filters is to allow the latter. The debugger would show you the actual location of the original exception.
Apr 5, 2014 at 4:10 PM
Sorry but I still don't get it. At least in Visual Studio I know just two places where you know the actual stack the error occurred:
  • The StackTrace property, for witch 'throw;' should be enough.
  • If you have have Debug > Exceptions > Break when an exception is > Common Language Runtime Exceptions > Thrown, then the a beautiful pop up shows in the exact callstack when then the exception is thrown, before any catch (or catch - if) has been executed.
I've actually tried:
        static void Main(string[] args)
        {
            try
            {
                RethrowSomething();
            }
            catch(Exception e)
            {
                Console.Write(e.Message); //Here I've a nice message 
            }
        }

        private static void RethrowSomething()
        {
            try
            {
                ThrowSomething();
            }
            catch (Exception e)
            {
                if (!e.Message.Contains("Simple"))
                    throw;
            }
        }

        private static void ThrowSomething()
        {
            throw new NotImplementedException("Complicated exception");
        }
At the global handler I get this nice stack trace
   at CatchThrow.Program.ThrowSomething() in d:\pruebas\CatchThrow\CatchThrow\Program.cs:line 38
   at CatchThrow.Program.RethrowSomething() in d:\pruebas\CatchThrow\CatchThrow\Program.cs:line 32
   at CatchThrow.Program.Main(String[] args) in d:\pruebas\CatchThrow\CatchThrow\Program.cs:line 15
And if I press 'Copy exception details to the clip board' I also get
System.NotImplementedException was caught
  HResult=-2147467263
  Message=Complicated exception
  Source=CatchThrow
  StackTrace:
       at CatchThrow.Program.ThrowSomething() in d:\pruebas\CatchThrow\CatchThrow\Program.cs:line 38
       at CatchThrow.Program.RethrowSomething() in d:\pruebas\CatchThrow\CatchThrow\Program.cs:line 32
       at CatchThrow.Program.Main(String[] args) in d:\pruebas\CatchThrow\CatchThrow\Program.cs:line 15
  InnerException: 
When you say
the debugger now shows you the location of the throw; statement, not the location of the original exception.
what VS windows you mean exactly?
Apr 5, 2014 at 4:45 PM
Edited Apr 5, 2014 at 4:47 PM
Try the following in Visual Studio:
  • Create a new Console application
  • In it, write some code that throws an exception (and do not catch it at all).
  • Make sure that exception type is not ticked in Debug → Exceptions.
  • Run it. The debugger will halt at the exception.
  • Stop the program again.
  • Now write a try/catch with a throw; statement inside the catch such that the above exception is caught and then rethrown.
  • Run it again. This time, the debugger will halt at the throw; statement, not at the original exception.
The example code that you provided should already demonstrate this, if it is running as a console application (or any other context that doesn’t handle all exceptions), and NotImplementedException is not ticked in Debug → Exceptions.
Apr 5, 2014 at 5:51 PM
Edited Apr 5, 2014 at 5:51 PM
Ok now I think I got it, there are three situations VS stops on exceptions:
  1. When its thrown and it's user-unhandled, if configured in Debug → Exceptions. (Second check)
  2. When its thrown, even if it's user handled, if configured in Debug → Exceptions. (First check, does it override the second check? What happens if you tick the first one and not the second one)
  3. When its a top level exception is not cached anywhere and is going to kill the application, independently of what Debug → Exceptions says.
This feature makes a difference only on the third case, am I right?

Thanks a lot
Apr 5, 2014 at 11:02 PM
Yes, although it’s not independent of what Debug → Exceptions says. It is relevant if the exception type is unticked in Debug → Exceptions. (Which is the case is most situations, since you won’t know beforehand what type of exception will be thrown.)
Apr 5, 2014 at 11:15 PM
So is it really worth it to introduce a new language feature just to preserve that specific debugging experience (provided that throw already exists and preserves the StackTrace property)? Is there any other benefit except the debugger behavior?
Apr 5, 2014 at 11:27 PM
Edited Apr 5, 2014 at 11:29 PM
I share your confusion, Stilgar; I didn’t expect this feature would make it into C# either. However, the benefits that I can see are:
  • the debugging experience as described
  • feature parity with VB.NET, which already has this
  • (as a consequence of the above) the ability to convert VB.NET (or indeed IL) code that uses this feature into C#
The implementation cost of the feature I imagine is very small. It requires no new keywords, no new CLR features, and very little and simple new C# grammar. I can’t judge the maintenance and documentation cost though.
Apr 5, 2014 at 11:36 PM
Timwi while I understand that other costs may be low there is still the cognitive and education burden for C# developers. As demonstrated in this very thread by me and Olmo the feature does add confusion.

I know this is not a democracy but if the C# team reads this and if there are no other benefits I would like to report negative feedback for this feature.
Apr 6, 2014 at 4:34 PM
I hope that the feature will be considered on the basis of its usefulness and benefit, not on the basis of whether you are intellectually capable of comprehending it.
Apr 6, 2014 at 4:47 PM
I personally for features like this. They increase the readability as soon as you understand the feature. Yeah sure it's one more feature to understand, but it's really easy to learn what it is, and reducing keywords/syntatic sugar for the sake of less constructs to learn would get you a language like C, and there's good reason why I don't code in C.

The thing that really sets C# apart from Java is it's amazing syntatic sugar. Most things done in C# can be done in Java, it just requires about 10x as much code, and makes it a nightmare to read. Reading C# code is very nice in contrast, and if a construct is used that you don't know, it's quick to google it/ask someone, and the five minutes spent doing that will save your hours of headache of reading ugly code.
Developer
Apr 6, 2014 at 9:16 PM
The primary reason is not for debugging, it's for crash dumps. Not only did you lose the stack trace in the debugger, you lost the stack trace in the dump, which basically makes the dump useless. If you get a null pointer dereference on some specific state in the client machine and Watson gives you a crash dump, if the dump is mangled in this way you basically have no information to go on.
Marked as answer by angocke on 4/6/2014 at 1:18 PM