Rethrowing a Wrapped Exception

On occasion, an exception thrown at a lower level in the stack will no longer make sense when caught at a higher level. For example, consider a System.IO.IOException that occurs because a system is out of disk space on the server. A client catching such an exception would not necessarily be able to understand the context of why there was even I/O activity. Similarly, consider a geographic coordinate request API that throws a System.UnauthorizedAccessException (an exception totally unrelated to the API called). In this second example, the caller has no context for understanding what the API call has to do with security. From the perspective of the code that invokes the API, these exceptions cause more confusion than they help diagnose. Instead of exposing such exceptions to the client, it might make sense to first catch the exception and then throw a different exception, such as InvalidOperationException (or even perhaps a custom exception), as a means of communicating that the system is in an invalid state. In such scenarios, be sure to set the InnerException property of the wrapping exception (generally via a constructor call such as new InvalidOperationException(string, Exception)) so that there is additional context that can be used for diagnostic purposes by someone closer to the framework that was invoked.

An important detail to remember when considering whether to wrap and rethrow an exception is the fact that the original stack trace—which provides the context of where the exception was thrown—will be replaced with the new stack trace of where the wrapping exception is thrown (assuming ExceptionDispatchInfo is not used). Fortunately, when the original exception is embedded into the wrapping exception, the original stack trace is still available.

Ultimately, the intended recipient of the exception is the programmer writing code that calls your API—possibly incorrectly. Therefore, you should provide as much information as possible to them to indicate both what the programmer did wrong and—perhaps more important—how to fix it. The exception type is a critical piece of the communication mechanism, so you must choose it wisely.

Guidelines
CONSIDER wrapping specific exceptions thrown from the lower layer in a more appropriate exception if the lower-layer exception does not make sense in the context of the higher-layer operation.
DO specify the inner exception when wrapping exceptions.
DO target developers as the audience for exceptions, identifying both the problem and the mechanism to resolve it, where possible.
Beginner Topic
Checked and Unchecked Conversions

As we first discussed in a Chapter 2 Advanced Topic, C# provides special keywords for marking a code block with instructions to the runtime for what should happen if the target data type is too small to contain the assigned data. By default, if the target data type cannot contain the assigned data, the data will be truncated during assignment. For an example, see Listing 11.6 with Output 11.1.

Listing 11.6: Overflowing an Integer Value
using System;
public class Program
{
    public static void Main()
    {
        // int.MaxValue equals 2147483647
        int n = int.MaxValue;
        n = n + 1;
        Console.WriteLine(n);
    }
}
Output 11.1
-2147483648

The code in Listing 11.6 writes the value -2147483648 to the console. However, placing the code within a checked block or using the checked option when running the compiler will cause the runtime to throw an exception of type System.OverflowException. The syntax for a checked block uses the checked keyword, as shown in Listing 11.7 with Output 11.2. If the calculation involves only constants, the calculation will be checked by default.

Listing 11.7: A Checked Block Example
using System;
public class Program
{
    public static void Main()
    {
        checked
        {
            // int.MaxValue equals 2147483647
            int n = int.MaxValue;
            n = n + 1;
            Console.WriteLine(n);
        }
    }
}

Output 11.2
Unhandled Exception: OverflowException: Arithmetic operation
resulted in an overflow. at Program.Main() in ...Program.cs:line 12

As shown in Output 11.2, an exception is thrown if, within the checked block, an overflow assignment occurs at runtime.9 Note that the location information in Output 11.2 (Program.cs:line X) appears only in debug compilations—that is, compilations using the /Debug option of the compiler.

To change an entire project to be unchecked or checked, set the CheckForOverflowUnderflow property to false or true, respectively.

<PropertyGroup>

     <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>

</PropertyGroup>

C# also supports an unchecked block that truncates the data instead of throwing an exception for assignments within the block (see Listing 11.8 with Output 11.3).

Listing 11.8: An Unchecked Block Example
using System;
public class Program
{
    public static void Main()
    {
        unchecked
        {
            // int.MaxValue equals 2147483647
            int n = int.MaxValue;
            n = n + 1;
            Console.WriteLine(n);
        }
    }
}
Output 11.3
-2147483648

Even if the checked option is on during compilation, the unchecked keyword in the code in Listing 11.8 will prevent the runtime from throwing an exception during execution.

Equivalent checked and unchecked expressions are available for cases where statements are not allowed. For example, a field initializer may consist of an expression rather than a statement:

int number = unchecked(int.MaxValue + 1);

________________________________________

9. In addition to Output 11.2, if the .NET Framework debugger is installed, a dialog may appear that prompts the user to send an error message to Microsoft, check for a solution, or debug the application.
{{ snackbarMessage }}
;