Exception Handling - Essential C# 6.0 (2016)

Essential C# 6.0 (2016)

10. Exception Handling

Chapter 4 discussed using the try/catch/finally blocks for standard exception handling. In that chapter, the catch block always caught exceptions of type System.Exception. This chapter defines some additional details of exception handling—specifically, details surrounding additional exception types, defining custom exceptions, and multiple catch blocks for handling each type. This chapter also details exceptions because of their reliance on inheritance.

Image

Multiple Exception Types

Listing 10.1 throws a System.ArgumentException, not the System.Exception type demonstrated in Chapter 4. C# allows code to throw any type that derives (perhaps indirectly) from System.Exception.

To throw an exception, you simply prefix the exception instance with the keyword throw. The type of exception used is obviously the type that best describes the circumstances surrounding the error that caused the exception.

For example, consider the TextNumberParser.Parse() method in Listing 10.1.

Begin 6.0

LISTING 10.1: Throwing an Exception


public sealed class TextNumberParser
{
public static int Parse(string textDigit)
{
string[] digitTexts =
{ "zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine" };

int result = Array.IndexOf(
digitTexts, textDigit.ToLower());

if (result < 0)
{
// Leveraging C# 6.0's nameof operator.
throw new ArgumentException(
"The argument did not represent a digit",
nameof(textDigit));
}

return result;
}
}


Instead of throwing System.Exception, it is more appropriate to throw ArgumentException because the type itself indicates what went wrong and includes special parameters for identifying which parameter was at fault.

Two similar exceptions are ArgumentNullException and NullReferenceException. ArgumentNullException should be thrown for the inappropriate passing of null arguments. This is a special case of an invalid parameter exception that would more generally (when it isn’t null) be thrown as an ArgumentException or an ArgumentOutOfRangeException. NullReferenceException is generally an exception that the underlying runtime will throw only with an attempt to dereference a null value—that is, an attempt to call a member on an object whose value is null. Instead of triggering a NullReferenceException to be thrown, programmers should check parameters for null before accessing them and then throw an ArgumentNullException, which can provide more contextual information, such as the parameter name. If there is an innocuous way to proceed even if an argument is null, be sure to use the C# 6.0 null propagation operator to avoid the runtime throwing a NullReferenceException.

One important characteristic of the argument exception types (including ArgumentNullException, ArgumentNullException, and ArgumentOutOfRangeException) is that each has a constructor parameter that allows identification of the argument name as a string. Prior to C# 6.0 this meant hardcoding a magic string (“textDigit”, for example) to identify the parameter name. The problem with this approach is that if the parameter name ever changed, developers had to remember to update the magic string. Fortunately, C# 6.0 provides a new operator,nameof, that takes the parameter name identifier and generates the parameter name at compile time (see nameof(textDigit) in Listing 10.1). The advantage of this approach is that now the IDE can use refactoring tools (such as automatic renaming) to change the identifier everywhere, including when it is used as an argument to the nameof operator. Additionally, if the parameter name changes (without the use of a refactoring tool), the compiler will generate an error if the identifier passed to the nameof operator no longer exists. Moving forward, with C# 6.0 (or later), the general guideline is to always use the nameof operator for the parameter name of an argument type exception.

Several other exceptions are intended only for the runtime and derive (sometimes indirectly) from System.SystemException. They include System.StackOverflowException, System.OutOfMemoryException,System.Runtime.InteropServices.COMException, System.ExecutionEngineException, and System.Runtime.InteropServices.SEHException. Do not throw exceptions of these types. Similarly, you should avoid throwing a System.Exception orSystem.ApplicationException, as these exceptions are so general that they provide little indication of the cause of or resolution to the problem. Instead, throw the most derived exception that fits the scenario. Obviously, developers should avoid creating APIs that could potentially result in a system failure. However, if the executing code reaches a certain state such that continuing to execute is unsafe or unrecoverable, it should call System.Environment.FailFast(). This will immediately terminate the process after writing a message to the Windows Application event log, and will even include the message as part of Windows Error Reporting if the user so chooses.


Guidelines

DO throw ArgumentException or one of its subtypes if bad arguments are passed to a member. Prefer the most derived exception type (ArgumentNullException, for example), if applicable.

DO set the ParamName property when throwing an ArgumentException or one of the subclasses.

DO use nameof for the paramName argument passed into argument exception types like ArgumentException, ArgumentOutOfRangeException, and ArgumentNullException that take such a parameter.

DO throw the most specific (most derived) exception that makes sense.

DO NOT throw a NullReferenceException. Instead, throw ArgumentNullException when a value is unexpectedly null.

DO NOT throw a System.SystemException or an exception type that derives from it.

DO NOT throw a System.Exception or System.ApplicationException.

CONSIDER terminating the process by calling System.Environment.FailFast() if the program encounters a scenario where it is unsafe for further execution.


End 6.0

Catching Exceptions

Throwing a particular exception type enables the catcher to use the exception’s type itself to identify the problem. It is not necessary, in other words, to catch the exception and use a switch statement on the exception message to determine which action to take in light of the exception. Instead, C# allows for multiple catch blocks, each targeting a specific exception type, as Listing 10.2 shows.

LISTING 10.2: Catching Different Exception Types


using System;

public sealed class Program
{
public static void Main(string[] args)
{
try
{
// ...
throw new InvalidOperationException(
"Arbitrary exception");
// ...
}
catch(Win32Exception exception)
when(exception.ErrorCode == 42)
{
// Handle Win32Exception where
// ErrorCode is 42.
}
catch (NullReferenceException exception)
{
// Handle NullReferenceException
}
catch (ArgumentException exception)
{
// Handle ArgumentException
}
catch (InvalidOperationException exception)
{
bool exceptionHandled=false;
// Handle InvalidOperationException
// ...
if(!exceptionHandled)
{
throw;
}
}
catch (SystemException)
{
// Handle SystemException
}
catch (Exception exception)
{
// Handle Exception
}
finally
{
// Handle any cleanup code here as it runs
// regardless of whether there is an exception
}
}
}


Listing 10.2 includes five catch blocks, each handling a different type of exception. When an exception occurs, the execution will jump to the catch block with the exception type that most closely matches the exception. The closeness of a match is determined by the inheritance chain. For example, even though the exception thrown is of type System.Exception, this “is a” relationship occurs through inheritance because System.InvalidOperationException ultimately derives from System.Exception. Since InvalidOperationException most closely matches the exception thrown, catch(InvalidOperationException...) will catch the exception instead of the catch(Exception...) block.

Starting with C# 6.0, an additional conditional expression is available for catch blocks. Rather than limiting whether a catch block matches based only on an exception type match, C# 6.0 adds support for a conditional clause. The when clause allows you to supply a Boolean expression; the catch block handles the exception only if the condition is true. In Listing 10.2, this is an equality comparison operator. Nevertheless, you could, for example, make a method call to validate a condition.

Of course, you could also simply place the conditional check as an if block within the catch body. However, doing so causes the catch block to become the handler for the exception before the condition is checked. It is difficult to write code that allows a different catch block to handle the exception in the scenario where the condition is not met. However, with the exception condition, it is now possible to examine the program state (including the exception) without having to catch and rethrow the exception.

Use conditional clauses with caution; if the conditional expression itself throws an exception, then that new exception is ignored and the condition is treated as false. For this reason, you should avoid throwing exceptions for the exception conditional expression.

Catch blocks must appear in order, from most specific to most general, to avoid a compile-time error. For example, moving the catch(Exception...) block before any of the other exceptions will result in a compile error, since all prior exceptions derive from System.Exceptionat some point in their inheritance chain.

As shown with the catch (SystemException){ }) block, a named parameter for the catch block is not required. In fact, a final catch without even the type parameter is allowable, as you will see in the next section.

Rethrowing an Existing Exception

In the InvalidOperationException catch block, a throw statement appears without any identification of the exception to throw (throw is on its own), even though an exception instance (exception) appears in the catch block scope that could be rethrown. Throwing a specific exception would update all the stack information to match the new throw location. As a result, all the stack information indicating the call site where the exception originally occurred would be lost, making it significantly more difficult to diagnose the problem. For this reason, C# supports a throw statement without the explicit exception reference as long as it occurs within a catch block. This way, code can examine the exception to determine if it is possible to fully handle it, and if not, rethrow the exception (even though not specified explicitly) as though it was never caught and without replacing any stack information.

Begin 5.0


Advanced Topic: Throwing Existing Exceptions without Replacing Stack Information

In C# 5.0, a mechanism was added that enables the throwing of a previously thrown exception without losing the stack trace information in the original exception. This allows you to rethrow exceptions, for example, even from outside a catch block and, therefore, without usingthrow;. Although it is fairly rare to need to do this, on some occasions exceptions are wrapped or saved until the program execution moves outside the catch block. For example, multithreaded code might wrap an exception with an AggregateException. The .NET Framework 4.5 provides a System.Runtime.ExceptionServices.ExceptionDispatchInfo class specifically to handle this scenario through the use of its static Catch() and instance Throw() methods. Listing 10.3 demonstrates rethrowing the exception without resetting the stack trace information or using an empty throw statement.

LISTING 10.3: Using ExceptionDispatchInfo to Rethrow an Exception


using System
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
Task task = WriteWebRequestSizeAsync(url);
try
{
while (!task.Wait(100))
{
Console.Write(".");
}
}
catch(AggregateException exception)
{
exception = exception.Flatten();
ExceptionDispatchInfo.Capture(
exception.InnerException).Throw();
}


With the ExeptionDispatchInfo.Throw() method, the compiler doesn’t treat it as a return statement in the same way that it might a normal throw statement. For example, if the method signature returned a value but no value was returned from the code path withExceptionDispatchInfo.Throw(), the compiler would issue an error indicating no value was returned. On occasion, therefore, developers may be forced to follow ExceptionDispatchInfo.Throw() with a return statement even though such a statement would never execute at runtime—the exception would be thrown instead.


End 5.0


Language Contrast: Java—Exception Specifiers

C# has no equivalent to Java’s exception specifiers. With exception specifiers, the Java compiler is able to verify that all possible exceptions thrown within a function (or a function’s call hierarchy) are either caught or declared as possibly rethrown. The C# team considered this option and concluded that the maintenance burden that it imposed was not worth the perceived benefit. Therefore, it is not necessary to maintain a list of all possible exceptions throughout a particular call stack, but neither is it feasible to easily determine the possible exceptions. (As it turns out, this wasn’t possible for Java, either. Calling virtual methods or using late binding, such as reflection, made it impossible to fully resolve at compile time which exceptions a method could possibly throw.)


Begin 2.0

General Catch Block

C# requires that any object that code throws must derive from System.Exception. However, this requirement is not universal to all languages. C/C++, for example, allows any object type to be thrown, including managed exceptions that don’t derive from System.Exception. Starting with C# 2.0, all exceptions, whether deriving from System.Exception or not, will propagate into C# assemblies as derived from System.Exception. The result is that System.Exception catch blocks will catch all exceptions not caught by earlier blocks.

C# also supports a general catch block (catch{ }) that behaves identically to the catch(System.Exception exception) block except that there is no type or variable name. Also, the general catch block must appear last within the list of catch blocks. Since the general catch block is identical to the catch(System.Exception exception) block and the general catch block must appear last, the compiler issues a warning if both exist within the same try/catch statement because the general catch block will never be invoked (see the Advanced Topic, “General Catch Blocks in C# 1.0,” for more information on general catch blocks).


Advanced Topic: General Catch Blocks in C# 1.0

In C# 1.0, if a non–System.Exception-derived exception was thrown from a method call (residing in an assembly not written in C#), the exception would not be caught by a catch(System.Exception) block. If a different language throws a string, for example, the exception could go unhandled. To avoid this, C# includes a catch block that takes no parameters. The term for such a catch block is general catch block, and Listing 10.4 includes one.

LISTING 10.4: Catching Any Exception


using System

public sealed class Program
{
public static void Main()
{
try
{
// ...
throw new InvalidOperationException (
"Arbitrary exception");
// ...
}
catch (NullReferenceException exception)
{
// Handle NullReferenceException
}
catch (ArgumentException exception)
{
// Handle ArgumentException
}
catch (InvalidOperationException exception)
{
// Handle ApplicationException
}
catch (SystemException exception)
{
// Handle SystemException
}
catch (Exception exception)
{
// Handle Exception
}
catch
{
// Any unhandled exception
}
finally
{
// Handle any cleanup code here as it runs
// regardless of whether there is an exception
}
}
}


The general catch block will catch all exceptions, regardless of whether they derive from System.Exception, assuming an earlier catch block does not catch them. The disadvantage of such a block is simply that there is no exception instance to access, and therefore no way to know the appropriate course of action. It wouldn’t even be possible to recognize the unlikely case where such an exception is innocuous. The best course of action is to handle the exception with some cleanup code before shutting down the application. The catch block could save any volatile data, for example, before shutting down the application or rethrowing the exception.


End 2.0


Advanced Topic: Empty Catch Block Internals

The CIL code corresponding to an empty catch block is, in fact, a catch(object) block. Thus, regardless of the type thrown, the empty catch block will catch it. Interestingly, it is not possible to explicitly declare a catch(object) exception block within C# code. Therefore, there is no means of catching a non–System.Exception-derived exception and having an exception instance to scrutinize.

In fact, unmanaged exceptions from languages such as C++ generally result in System.Runtime.InteropServices.SEHException-type exceptions, which derive from the System.Exception type. Therefore, not only can the unmanaged type exceptions be caught using a general catch block, but the non–System.Exception-managed types that are thrown can be caught as well—for instance, types such as string.


Guidelines for Exception Handling

Exception handling provides much-needed structure to the error-handling mechanisms that preceded it. However, it can still lead to some unwieldy results if used haphazardly. The following guidelines offer some best practices for exception handling.

Catch only the exceptions that you can handle.

Generally it is possible to handle some types of exceptions but not others. For example, opening a file for exclusive read-write access may throw a System.IO.IOException because the file is already in use. In catching this type of exception, the code can report to the user that the file is in use and allow the user the option of canceling the operation or retrying it. Only exceptions for which there is a known action should be caught. Other exception types should be left for callers higher in the stack.

Don’t hide (bury) exceptions you don’t fully handle.

New programmers are often tempted to catch all exceptions and then continue executing instead of reporting an unhandled exception to the user. However, this practice may result in a critical system problem going undetected. Unless code takes explicit action to handle an exception or explicitly determines certain exceptions to be innocuous, catch blocks should rethrow exceptions instead of catching them and hiding them from the caller. In most cases, catch(System.Exception) and general catch blocks should occur higher in the call stack, unless the block ends by rethrowing the exception.

Begin 4.0

Use System.Exception and general catch blocks rarely.

Almost all exceptions derive from System.Exception. However, the best way to handle some System.Exceptions is to allow them to go unhandled or to gracefully shut down the application sooner rather than later. These exceptions include things such asSystem.OutOfMemoryException and System.StackOverflowException. In CLR 4, such exceptions defaulted to nonrecoverable, such that catching them without rethrowing them would cause the CLR to rethrow them anyway. These exceptions are runtime exceptions that the developer cannot write code to recover from. Therefore, the best course of action is to shut down the application—something the runtime will force in CLR 4 and later. Code prior to CLR 4 should catch such exceptions only to run cleanup or emergency code (such as saving any volatile data) before shutting down the application or rethrowing the exception with throw;.

Avoid exception reporting or logging lower in the call stack.

Often, programmers are tempted to log exceptions or report exceptions to the user at the soonest possible location in the call stack. However, these locations are seldom able to handle the exception fully; instead, they resort to rethrowing the exception. Such catch blocks should not log the exception or report it to a user while in the bowels of the call stack. If the exception is logged and rethrown, the callers higher in the call stack may do the same, resulting in duplicate log entries of the exception. Worse, displaying the exception to the user may not be appropriate for the type of application. (Using System.Console.WriteLine() in a Windows application will never be seen by the user, for example, and displaying a dialog in an unattended command-line process may go unnoticed and freeze the application.) Logging- and exception-related user interfaces should be reserved for use higher up in the call stack.

Use throw; rather than throw <exception object> inside a catch block.

It is possible to rethrow an exception inside a catch block. For example, the implementation of catch(ArgumentNullException exception) could include a call to throw exception. However, rethrowing the exception like this will reset the stack trace to the location of the rethrown call, instead of reusing the original throw point location. Therefore, unless you are rethrowing with a different exception type or intentionally hiding the original call stack, use throw; to allow the same exception to propagate up the call stack.

Begin 6.0

Avoid throwing exceptions from exception conditionals.

When providing an exception conditional, avoid code that throws an exception. Doing so will result in a false condition and the exception occurrence will be ignored. For this reason, consider placing complicated conditional checks into a separate method that is wrapped in a try/catch block that handles the exception explicitly.

Avoid exception conditionals that might change over time.

If an exception conditional evaluates conditions such as exception messages that could potentially change with localization or changed message, the expected exception condition will not get caught, unexpectedly changing the business logic. For this reason, ensure exception conditions are valid over time.

End 6.0

Use caution when rethrowing different exceptions.

From inside a catch block, rethrowing a different exception will not only reset the throw point, but also hide the original exception. To preserve the original exception, set the new exception’s InnerException property, generally assignable via the constructor. Rethrowing a different exception should be reserved for the following situations:

1. Changing the exception type clarifies the problem.

For example, in a call to Logon(User user), rethrowing a different exception type is perhaps more appropriate than propagating System.IO.IOException when the file with the user list is inaccessible.

2. Private data is part of the original exception.

In the preceding scenario, if the file path is included in the original System.IO.IOException, thereby exposing private security information about the system, the exception should be wrapped. This assumes, of course, that InnerException is not set with the original exception. (Funnily enough, a very early version of CLR v1 [pre-alpha, even] had an exception that said something like “Security exception: You do not have permission to determine the path of c:\temp\foo.txt”.)

3. The exception type is too specific for the caller to handle appropriately.

For example, instead of throwing an exception specific to a particular database system, a more generic exception is used so that database-specific code higher in the call stack can be avoided.


Guidelines

AVOID exception reporting or logging lower in the call stack.

DO NOT over-catch. Exceptions should be allowed to propagate up the call stack unless it is clearly understood how to programmatically address those errors lower in the stack.

CONSIDER catching a specific exception when you understand why it was thrown in a given context and can respond to the failure programmatically.

AVOID catching System.Exception or System.SystemException except in top-level exception handlers that perform final cleanup operations before rethrowing the exception.

DO use throw rather than throw <exception object> inside a catch block.

DO use caution when rethrowing different exceptions.

DO NOT throw a NullRefernceException, favoring ArgumentNullException instead when a value is unexpectedly null.

AVOID throwing exceptions from exception conditionals.

AVOID exception conditionals that might change over time.


Defining Custom Exceptions

Once throwing an exception becomes the best course of action, it is preferable to use framework exceptions because they are well established and understood. Instead of throwing a custom invalid argument exception, for example, it is preferable to use the System.ArgumentExceptiontype. However, if the developers using a particular API will take special action—the exception-handling logic will vary to handle a custom exception type, for instance—it is appropriate to define a custom exception. For example, if a mapping API receives an address for which the ZIP code is invalid, instead of throwing System.ArgumentException, it may be better to throw a custom InvalidAddressException. The key is whether the caller is likely to write a specific InvalidAddressException catch block with special handling rather than just a genericSystem.ArgumentException catch block.

Defining a custom exception simply involves deriving from System.Exception or some other exception type. Listing 10.5 provides an example.

LISTING 10.5: Creating a Custom Exception


class DatabaseException : System.Exception
{
public DatabaseException(
System.Data.SqlClient.SQLException exception)
{
InnerException = exception;
// ...
}

public DatabaseException(
System.Data.OracleClient.OracleException exception)
{
InnerException = exception;
// ...
}

public DatabaseException()
{
// ...
}

public DatabaseException(string message)
{
// ...
}

public DatabaseException(
string message, Exception innerException)
{
InnerException = innerException;
// ...
}
}


This custom exception might be created to wrap proprietary database exceptions. Since Oracle and SQL Server (for example) throw different exceptions for similar errors, an application could define a custom exception that standardizes the database-specific exceptions into a common exception wrapper that the application can handle in a standard manner. That way, whether the application was using an Oracle or a SQL Server back-end database, the same catch block could be used to handle the error higher up the stack.

The only requirement for a custom exception is that it derives from System.Exception or one of its descendants. However, there are several more good practices for custom exceptions:

• All exceptions should use the “Exception” suffix. This way, their purpose is easily established from their name.

• Generally, all exceptions should include constructors that take no parameters, a string parameter, and a parameter set consisting of a string and an inner exception. Furthermore, since exceptions are usually constructed within the same statement in which they are thrown, any additional exception data should also be allowed as part of the constructor. (The obvious exception to creating all these constructors is if certain data is required and a constructor circumvents the requirements.)

• The inheritance chain should be kept relatively shallow (with fewer than approximately five levels).

The inner exception serves an important purpose when rethrowing an exception that is different from the one that was caught. For example, if a System.Data.SqlClient.SqlException is thrown by a database call but is caught within the data access layer to be rethrown as aDatabaseException, the DatabaseException constructor that takes the SqlException (or inner exception) will save the original SqlException in the InnerException property. That way, when requiring additional details about the original exception, developers can retrieve the exception from the InnerException property (for example, exception.InnerException).


Guidelines

DO NOT create a new exception type if the exception would not be handled differently than an existing CLR exception. Throw the existing framework exception instead.

DO create a new exception type to communicate a unique program error that cannot be communicated using an existing CLR exception and can be programmatically handled in a different way than any other existing CLR exception type.

DO provide a parameterless constructor on all custom exception types. Also provide constructors that take a message and an inner exception.

DO end exception class names with the “Exception” suffix.

DO make exceptions runtime-serializable.

CONSIDER providing exception properties for programmatic access to extra information relevant to the exception.

AVOID deep exception hierarchies.



Advanced Topic: Serializable Exceptions

Serializable objects are objects that the runtime can persist into a stream—a file stream, for example—and then be reinstantiated out of the stream. In the case of exceptions, this behavior may be necessary for certain distributed communication technologies. To support serialization, exception declarations should either include the System.SerializableAttribute attribute or implement ISerializable. Furthermore, they must include a constructor that takes System.Runtime.Serialization.SerializationInfo andSystem.Runtime.Serialization.StreamingContext. Listing 10.6 shows an example of using System.SerializableAttribute.

LISTING 10.6: Defining a Serializable Exception


// Supporting serialization via an attribute
[Serializable]
class DatabaseException : System.Exception
{
// ...

// Used for deserialization of exceptions
public DatabaseException(
SerializationInfo serializationInfo,
StreamingContext context)
{
//...
}

}


The preceding DatabaseException example demonstrates both the attribute and the constructor requirement for making an exception serializable.


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 or understanding of 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 the 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 to her that indicates both what the programmer did wrong and—perhaps more importantly—how to fix it. The exception type is a critical piece of the communication mechanism. Therefore, you must choose the type carefully.


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.

DO use an empty throw statement (throw;) when rethrowing the same exception rather than passing the exception as an argument to throw.



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 of 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 truncate during assignment. For an example, see Listing 10.7.

LISTING 10.7: Overflowing an Integer Value


using System;

public class Program
{
public static void Main()
{
// int.MaxValue equals 2147483647
int n = int.MaxValue;
n = n + 1 ;
System.Console.WriteLine(n);
}
}


The results of Listing 10.7 appear in Output 10.1.

OUTPUT 10.1

-2147483648

The code in Listing 10.7 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 10.8.

LISTING 10.8: 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 ;
System.Console.WriteLine(n);
}
}
}


If the calculation involves only constants, the calculation will be checked by default. The results of Listing 10.8 appear in Output 10.2.

OUTPUT 10.2

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

In addition, depending on the version of Windows and whether a 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. Also, the location information (Program.cs:line X) will appear only in debug compilations—that is, compilations using the /Debug option of the Microsoft csc.exe compiler. The result is that an exception is thrown if, within the checked block, an overflow assignment occurs at runtime.

The C# compiler provides a command-line option for changing the default checked behavior from unchecked to checked. C# also supports an unchecked block that truncates the data instead of throwing an exception for assignments within the block (see Listing 10.9).

LISTING 10.9: 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 ;
System.Console.WriteLine(n);
}
}
}


The results of Listing 10.8 appear in Output 10.3.

OUTPUT 10.3

-2147483648

Even if the checked option is on during compilation, the unchecked keyword in the code in Listing 10.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);


Summary

Throwing an exception causes a significant performance hit. A single exception causes lots of runtime stack information to be loaded and processed—data that would not otherwise be loaded—and it takes a considerable amount of time to handle. As pointed out in Chapter 4, you should use exceptions only to handle exceptional circumstances; APIs should provide mechanisms to check whether an exception will be thrown instead of forcing a particular API to be called to determine whether an exception will be thrown.

The next chapter introduces generics—a C# 2.0 feature that significantly enhances code written in C# 1.0. In fact, it essentially deprecates any use of the System.Collections namespace, which was formerly used in nearly every project.