Are Exceptions and return statements the only possible early exits in C#?

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.


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


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(+);


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!

Tags:

C#

.Net