19

For example, I'm curious if in the following code, I can be assured that either Foo() or Bar() will be executed.

try {
    ... // Assume there is no return statement here
    Foo();
} catch (Exception e) {
    Bar();
}

I am quite familiar with finally blocks, and do not need an explanation of that feature.

I ask because the code above would not work in Java due to the existence of Throwable, which I discovered the hard way.

pamphlet
  • 2,054
  • 1
  • 17
  • 27
  • 3
    Depends on if you count System.Exit() as an early exit – Jesus Ramos Mar 06 '13 at 22:33
  • 2
    There is also the witchcraft known as `yield return`. Not sure how those behave though, since I don't practice that form of witchcraft. Maybe they're similar to returns. – Alex Mar 06 '13 at 22:35
  • 5
    Can a `goto` jump out of a `try` block? Without more context, it's hard to say. If that `try` is inside a `while`, for example, then `break` and `continue` could cause `Foo` not to be executed. – Jim Mischel Mar 06 '13 at 22:35
  • 3
    There could also be an infinite loop before `Foo()`. Then neither function will be executed. :-) – Elian Ebbing Mar 06 '13 at 22:37
  • 4
    If `Foo` thows both will execute (which is != either...or, isn't it?). – Tim Schmelter Mar 06 '13 at 22:39
  • This is all super helpful and interesting! – pamphlet Mar 06 '13 at 22:40
  • 1
    Process.GetCurrentProcess().Kill() is the earliest exit I guess.. – Olexander Ivanitskyi Mar 06 '13 at 22:41
  • 4
    There could also be a `StackOverflowException` before `Foo()`. From MSDN: Starting with the .NET Framework version 2.0, a StackOverflowException object cannot be caught by a try-catch block and the corresponding process is terminated by default. – Elian Ebbing Mar 06 '13 at 22:42
  • Exception in unmanaged code will not be intercepted by catch block with parameter. – Olexander Ivanitskyi Mar 06 '13 at 22:51
  • 3
    So who wants to create the community wiki answer with all these comments? ;-) – Tim Schmelter Mar 06 '13 at 22:53
  • 2
    I will if no one else does. :) Jim's `goto` suggestion needs some follow-up. I can experiment on that. @Olexander, do you have a reference for your comment on unmanaged exceptions? – pamphlet Mar 06 '13 at 22:57
  • Some info on unmanaged exceptions here: http://msdn.microsoft.com/en-us/library/dd638517.aspx. Also, `goto` can indeed jump out of a `try`. – Jim Mischel Mar 06 '13 at 23:11
  • I can't say for certain, but on some platforms (Windows Phone, WinRT, Xamarin.Android, Xamarin.iOS) if your app is terminated by the operating system (taking to long to suspend for example), I'm not sure if a catchable exception (or any exception) is thrown. I suspect this might be considered "out of scope" here anyway, on the order of BSOD. – Chris Sinclair Mar 06 '13 at 23:13
  • if you put `if(false)` on the preceding line, both don't execute. – Moop Mar 06 '13 at 23:17
  • 1
    An unhandled exception in a thread will terminate the program. Neither `Foo` nor `Bar` will execute in that case. – Jim Mischel Mar 06 '13 at 23:22
  • @pamphlet, seems that unmanaged exceptions were not caught by `catch(Exception)` block only before .NET 2.0 http://stackoverflow.com/questions/1531077/will-clr-handle-both-cls-complaint-and-non-cls-complaint-exceptions in .NET 2.0+ non-CLS compliant exceptions are wrapped by RuntimeWrappedException – Olexander Ivanitskyi Mar 06 '13 at 23:25
  • @Olexander: I'm not sure how to reconcile the answer to that question, and this one: http://stackoverflow.com/a/5793564/56778 – Jim Mischel Mar 06 '13 at 23:41
  • 1
    Could use the debugger and [Set Next Statement](http://www.codeproject.com/Articles/79508/Mastering-Debugging-in-Visual-Studio-2010-A-Beginn#heading0009) to skip it too... but that's cheating... right? – Chris Sinclair Mar 07 '13 at 00:02
  • 1
    @JimMischel, @pamphlet, just to summarize shortly: 1) Prior to .NET 2.0 all unmanaged exceptions could be caught by empty `catch` block only. 2) Starting with .NET 2.0 unmanaged exceptions can be caught by `catch(Exception)` block as they are wrapped by `RuntimeWrappedException` (inherited from `Exception`). 3) Starting with .NET 4.0 corrupted process state exceptions (as access violation exception) are not delivered to managed code and cannot be handled by `try/catch` block unless the `` key or HandleProcessCorruptedStateExceptionsAttribute is specified. – Olexander Ivanitskyi Mar 07 '13 at 00:21
  • Another concern could be if you're being called by hostile code that has access to Exception Filters (e.g. not written in C#, but in VB.Net). If your code is about to handle an `Exception`, the code written in an exception filter in an outer scope will run first - allowing the hostile code to alter other state before `Bar()` executes (if at all, if the exception filter never returns) – Damien_The_Unbeliever Mar 07 '13 at 07:20
  • I have added a summarized community wiki answer, fell free to expand the answer if needed. – Olexander Ivanitskyi Mar 09 '13 at 17:59

4 Answers4

6

There are also the "burn-down-the-house" ways of stopping an application:

Environment.Exit(int code);
Environment.FailFast(string message);
Thread.CurrentThread.Abort();
AppDomain.Unload(AppDomain.CurrentDomain);

For fun, here's another :)

[DllImport("kernel32.dll",SetLastError = true)]
static extern bool WriteProcessMemory(
      IntPtr hProcess, 
      IntPtr lpBaseAddress, 
      byte [] lpBuffer, 
      uint nSize, 
      out UIntPtr lpNumberOfBytesWritten);

var myProcess = Process.GetCurrentProcess();
var hProcess = myProcess.Handle;
var rnd = new Random();
while(true)
{
    var writeTo = new IntPtr((int)rnd.Next(0, int.MaxValue));
    var toWrite = new byte[1024];
    UIntPtr written;
    WriteProcessMemory(
        hProcess, 
        writeTo, 
        toWrite, 
        (uint)toWrite.Length, 
        out written);
}

Out of curiosity and prodding, let's take them for a test drive!

Our test rig:

    static void Main(string[] args)
    {
        Trace.Listeners.Add(new ConsoleTraceListener());
        AppDomain.CurrentDomain.UnhandledException += OnNoes;
        try
        {
            // INSERT BURN STATEMENT
            Foo();
        }
        catch (Exception e)
        {
            Bar();
        }
        finally
        {
            Baz();
        }
    }

    static void Foo()
    {
        Trace.WriteLine("I AM FOO!");
    }
    static void Bar()
    {
        Trace.WriteLine("I AM BAR!");
    }
    static void Baz()
    {
        Trace.WriteLine("I AM BAZ!");
    }
    static void OnNoes(object sender, UnhandledExceptionEventArgs e)
    {
        Trace.WriteLine("OhNoes!");
    }

The results!

The Burn Statement:

Thread.CurrentThread.Abort();

Output:

I AM BAR!
I AM BAZ!

The Burn Statement:

AppDomain.Unload(AppDomain.CurrentDomain);

Output:

I AM BAR!
I AM BAZ!

The Burn Statement:

Environment.Exit(-1);

Output:

Nothing! No trace output at all!

The Burn Statement:

Environment.FailFast("Burn!!!");

Output:

Application crash! A FatalExecutionEngineError was thrown, 
which was not caught by any block/handler. No trace output.

So there you go! What? I missed one?

The Burn Statement:

Splode();

Where "Splode" is:

    static void Splode()
    {
        var myProcess = Process.GetCurrentProcess();
        var hProcess = myProcess.Handle;
        var rnd = new Random();
        while (true)
        {
            var writeTo = new IntPtr((int)rnd.Next(0, int.MaxValue));
            var toWrite = new byte[1024];
            UIntPtr written;
            WriteProcessMemory(
                hProcess,
                writeTo,
                toWrite,
                (uint)toWrite.Length,
                out written);
        }            
    }

Output:

Application crash! A FatalExecutionEngineError was thrown, 
which was not caught by any block/handler. No trace output.
Crashed Visual Studio while running attached!
JerKimball
  • 16,584
  • 3
  • 43
  • 55
5

Yes... the most obvious ones are await, yield break/yield return, goto, if(false), etc., as mentioned in comments. But all of these statements/expressions have to be written by yourself, in the method containing your try statement, so you don't really have to worry about them.

However, even apart from these, there is a way to exit without throwing an exception or returning (or running either of those two methods). That is... throwing something that is not an exception.

The C# language specification states that the only things you can throw are either instances of the class Exception, or the null literal (in which case a NullReferenceException is thrown.) Found in §8.9.5:

The [throw] expression must denote a value of the class type System.Exception, of a class type that derives from System.Exception or of a type parameter type that has System.Exception (or a subclass thereof) as its effective base class. If evaluation of the expression produces null, a System.NullReferenceException is thrown instead.

However, this restriction only restricts C# code. C# code is compiled into Intermediate Language, which is not restricted in this way. Also found in the C# language specification, §8.10:

Some programming languages may support exceptions that are not representable as an object derived from System.Exception, although such exceptions could never be generated by C# code.

In order to catch these exceptions, you need to use a general catch clause, like so:

try
{
    //...
    Foo();
}
catch
{
    Bar();
}

NOTE: This method only applies if you compile to a .NET framework before version 2.0. Starting in that version, the CLR wraps the thrown object in a RuntimeWrappedException. Thanks, svick!

In addition, a few other people mentioned killing the process, or throwing a StackOverflowException, both of which will work well to accomplish this task. There may be other ways apart from these, but I don't think so (apart from suddenly unplugging the computer, heaven forbid.) Hope this helps! --Brandon

leviathanbadger
  • 1,682
  • 15
  • 23
  • 3
    This isn't true since .Net 2.0, because all thrown non-`Exception`s are wrapped in `RuntimeWrappedException` by the CLR. – svick Mar 07 '13 at 00:20
  • Really? I should check the CLR spec. Either way, the C# spec hasn't been updated to reflect this. (Actually, I don't think that the C# spec is tied to any particular CLR version. You may be right!) – leviathanbadger Mar 07 '13 at 00:22
3

Assume we have the following code:

try
{
    /*Breaking statement goes here*/

    Foo();
}
catch (Exception ex)
{
    Bar();
}
finally
{
    Baz();
}

I would split breakers into 3 common reasons:

  1. Code flow statements:

    1.1. return: Foo(-); Bar(-); Baz(+);

    1.2. goto: Foo(-); Bar(-); Baz(+);

    1.3. if(false): Foo(-); Bar(-); Baz(+);

    1.4. while(true){}: Foo(-); Bar(-); Baz(-);

    1.5. yield return, in case the method returns IEnumerable and yield return comes before the try block: Foo(-); Bar(-); Baz(-);

    1.6. yield break, in case the method returns IEnumerable: Foo(-); Bar(-); Baz(-);

    1.7. break, in case the code is wrapped by a cycle: Foo(-); Bar(-); Baz(+);

    1.8. continue, in case the code is wrapped by a cycle: Foo(-); Bar(-); Baz(+);

  2. Process/domain/thread termination.

    2.1. Process.GetCurrentProcess().Kill(): Foo(-); Bar(-); Baz(-);

    2.2. Environment.Exit(0): Foo(-); Bar(-); Baz(-);

    2.3. Environment.FailFast(""): Foo(-); Bar(-); Baz(-);

    2.4. AppDomain.Unload(AppDomain.CurrentDomain): Foo(-); Bar(+); Baz(+);

    2.5. Thread.CurrentThread.Abort(): Foo(-); Bar(+); Baz(+);

  3. Unhandled exceptions.

    3.1. Exception in unmanaged code prior to .NET 2.0: Foo(-); Bar(-); Baz(+);

    3.2. Exception in unmanaged code since .NET 2.0: Foo(-); Bar(+); Baz(+);

    3.3. Corrupted process state exception since .NET 4.0 (nor <legacyCorruptedStateExceptionsPolicy> neither HandleProcessCorruptedStateExceptionsAttribute is specified): Foo(-); Bar(-); Baz(+);

    3.4. Corrupted process state exception prior to .NET 4.0 or <legacyCorruptedStateExceptionsPolicy> or HandleProcessCorruptedStateExceptionsAttribute is specified: Foo(-); Bar(+); Baz(+);

    3.5. Exception in another thread since .NET 2.0 and <legacyUnhandledExceptionPolicy> is not enabled: Foo(-); Bar(-); Baz(-);

    3.6. Exception in another thread prior to .NET 2.0 or <legacyUnhandledExceptionPolicy> is enabled: Foo(+); Bar(-); Baz(+);

Olexander Ivanitskyi
  • 2,202
  • 17
  • 32
1

There are also a couple of other options (one mentioned in The Daily WTF when a company thought transactions caught everything), namely the operating system shutting down, the task being killed and power being lost in the machine (don't laugh, it happens). Some of these you can cleanly handle using Form_Closing events for example (OS shutdown) but some you just can't. The operating system, and other applications (e.g., VS when it crashes), gets around these circumstances by preserving a temporary state every X minutes. If that state exists then the application was terminated unexpectedly. If such a state doesn't exist then the application exited cleanly and data was saved correctly.

DiskJunky
  • 4,750
  • 3
  • 37
  • 66