December Code

C# exception handling interview questions, with answers

Exception questions in C# test two things: whether you know exactly what runs when an exception is thrown, and whether you release resources correctly when it is. They ask when finally does and does not run, how rethrowing changes a stack trace, what a when filter adds, and how using and IDisposable stand in for the destructors C# does not have. Every answer below comes with code whose output was produced by compiling and running it on .NET 10.

The questions start with the try statement, move through rethrowing, filtering and custom exceptions, and end with the patterns for releasing resources. Then take the free C# diagnostic — ten questions across every C# topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.How do try, catch and finally work in C#, and when does finally not run?

    In short: catch clauses are tried in order and the first that matches handles the exception; finally runs whether the block completes, returns or throws, unless the process itself is ending.

    When code in try throws, the runtime searches the catch clauses in order and runs the first whose type matches, including base types, so specific exceptions must be listed before general ones. The finally block then runs whether try completed normally, returned early or threw, which makes it the place for cleanup, and a return inside try still runs finally first, as below. finally does not run when the process ends abruptly: Environment.FailFast, a StackOverflowException, or the process being killed. An exception thrown from inside finally replaces the one already in flight, which is then lost. A catch that silently swallows exceptions is the most common misuse; catch only what you can handle.

    Console.WriteLine(Read());
    
    static string Read() {
        try {
            return "try";
        } finally {
            Console.Write("fin ");
        }
    }
    // fin try
  2. 2.How should you rethrow or wrap an exception in C#?

    In short: Use throw; to rethrow the same exception with its original stack trace, or throw a new exception with the original as InnerException to add context; throw ex; loses where the error began.

    Inside a catch block, throw; with no operand rethrows the exception being handled and keeps its stack trace, so logs still point at the line that failed. throw ex; throws the same object as if it were new and resets the trace to the catch block, which is why code analysers flag it. When a low-level error should surface as a higher-level one, wrap it: throw a new exception with a message that explains the context and pass the original as the inner exception, as Load does below, so nothing is lost. ExceptionDispatchInfo.Capture(ex).Throw() rethrows an exception captured earlier, even on another thread, with its original trace, which is how await rethrows a task's exception.

    try {
        Load();
    } catch (Exception e) {
        var i = e.InnerException;
        var m = e.Message;
        Console.WriteLine(m);
        Console.WriteLine(
            i?.GetType().Name);
    }
    
    static void Load() {
        try {
            int.Parse("x");
        } catch (Exception f) {
            throw new Exception(
                "bad config", f);
        }
    }
    // bad config
    // FormatException
  3. 3.What does a when filter on a catch clause do in C#?

    In short: catch (T e) when (condition) handles the exception only if the condition is true; otherwise that clause is skipped as if it did not match, and the stack is not unwound to test it.

    A filter makes the condition part of the match: a clause for a request exception with when (e.Code == 404) handles only that case and lets every other failure continue to the next clause or up the stack, untouched, as below. Because filters run before the stack unwinds, a debugger or crash dump still shows the frame that originally threw, and a filter that logs and returns false can record every exception without handling any of them. Filters replace the older pattern of catching, testing a condition and rethrowing, which unwinds the stack for nothing. They are also cheaper to read: the conditions under which a handler applies are written where the handler is declared.

    int[] codes = { 404, 500 };
    foreach (var c in codes) {
        try {
            throw new HttpErr(c);
        } catch (HttpErr e)
            when (e.Code == 404) {
            Say("missing");
        } catch (HttpErr e) {
            Say("error " + e.Code);
        }
    }
    
    static void Say(string s) =>
        Console.WriteLine(s);
    
    class HttpErr(int code)
        : Exception
    {
        public int Code => code;
    }
    // missing
    // error 500
  4. 4.How do you write a custom exception class in C#?

    In short: Derive from Exception, or a more specific built-in type, name it ending in Exception, provide the standard constructors, and put extra data in read-only properties.

    A custom exception is worth writing when callers need to catch that failure specifically or read data from it; otherwise a built-in type such as ArgumentException or InvalidOperationException says enough. Derive from Exception, or from the built-in type it refines, end the name in Exception, and add the conventional constructors: parameterless, with a message, and with a message and an inner exception, so callers can wrap errors without losing the cause. Put extra information in read-only properties, as Balance below, rather than only in the message text, so code can act on it. Serialization constructors are no longer needed on modern .NET, where binary serialization of exceptions is obsolete.

    NsfException? x = null;
    try {
        Withdraw(500, 200);
    } catch (NsfException e) {
        x = e;
    }
    Console.WriteLine(x!.Message);
    Console.WriteLine(x.Balance);
    
    static void Withdraw(int amt,
        int bal) {
        if (amt > bal)
            throw new NsfException(
                "short", bal);
    }
    
    class NsfException(
        string msg, int balance)
        : Exception(msg)
    {
        public int Balance =>
            balance;
    }
    // short
    // 200
  5. 5.What is IDisposable, and what does the using statement do in C#?

    In short: IDisposable marks a type holding a resource that needs prompt release through Dispose(); using calls Dispose automatically when the block or scope ends, even if an exception is thrown.

    The garbage collector reclaims memory, but not at a predictable time, and it knows nothing about file handles, sockets, database connections or locks. Types holding such resources implement IDisposable, whose Dispose method releases them now. A using statement compiles to a try/finally that calls Dispose when the block exits, however it exits, and C# 8's using declaration, using var r = ...;, disposes at the end of the enclosing scope, as below. Resources are disposed in reverse order of acquisition, so b is released before a. For asynchronous cleanup there is IAsyncDisposable with await using, which streams and database connections use to flush over the network.

    Run();
    
    static void Run()
    {
        using var a = new Res("a");
        using var b = new Res("b");
        Console.WriteLine("work");
    }
    
    class Res(string n)
        : IDisposable
    {
        public void Dispose() =>
            Console.WriteLine(
                n + " done");
    }
    // work
    // b done
    // a done
  6. 6.What is the difference between Dispose and a finalizer in C#?

    In short: Dispose is called deterministically by your code, usually through using; a finalizer, ~T(), is called by the garbage collector at an unpredictable time and is only a safety net.

    A finalizer looks like a C++ destructor, written ~FileHandle(), but it runs on the garbage collector's finalizer thread at some point after the object becomes unreachable, possibly not before the process exits, and objects with finalizers need an extra collection before their memory is reclaimed. It is therefore only a backstop for a class that directly owns an unmanaged handle. The dispose pattern combines the two: Dispose releases the resource and calls GC.SuppressFinalize(this) so the finalizer is skipped, while the finalizer releases only unmanaged state if Dispose was never called. Most classes need no finalizer at all, because they hold managed wrappers such as SafeHandle or FileStream, which already have one.

  7. 7.Does C# have checked exceptions like Java?

    In short: No: a C# method never declares the exceptions it throws and callers are never forced to catch them, so documentation carries that information instead.

    Java splits exceptions into checked ones, which a method must declare with throws and a caller must catch or declare in turn, and unchecked ones. C#'s designers left checked exceptions out, citing versioning, since adding an exception to a method breaks every caller, and scale, since signatures in large systems accumulate long throws lists that callers answer with empty catch blocks. In C# every exception behaves like an unchecked one: the compiler never forces a catch, and the exceptions a method can throw are documented in <exception> XML comments. The practical consequence is discipline: catch where you can recover or add context, and let everything else reach a top-level handler that logs it.

How the diagnostic asks it

One question from the C# bank, exactly as a sitting would show it. The bank has 4 on exceptions & resources and 30 across C#.

Exceptions & Resources · easyCSHARP-027

What does this C# code print?

using (var r = new Resource())
{
    Console.Write("body ");
}
Console.Write("after");

class Resource : IDisposable {
    public Resource() => Console.Write("open ");
    public void Dispose() => Console.Write("close ");
}
  1. 1open body after close
  2. 2open body close aftercorrect
  3. 3open close body after
  4. 4open body after

A using statement guarantees that Dispose is called when control leaves its block, as if the block were a try with Dispose in its finally. So the resource opens, the body runs, and as soon as the block ends Dispose prints close, before the next statement: open body close after. open body after close assumes disposal waits until the program ends or the garbage collector runs; using exists so that it does not. open close body after disposes too early. open body after forgets Dispose entirely, which is what happens without using. Since C# 8, using var r = new Resource(); disposes at the end of the enclosing scope instead.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 C# questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated