Handling Errors Using Exceptions - Programming in C# - Sams Teach Yourself C# 5.0 in 24 Hours (2013)

Sams Teach Yourself C# 5.0 in 24 Hours (2013)

Part II: Programming in C#

Hour 11. Handling Errors Using Exceptions


What You’ll Learn in This Hour

Understanding exceptions

System.Exception

Throwing exceptions

Handling exceptions

Rethrowing caught exceptions

Overflow and integer arithmetic

Exceptions, code contracts, and parameter validation


As an application executes, it can encounter any number of possible error conditions. C# handles these error conditions using exceptions, which encapsulate the information about an error in a single class. Exceptions are intended to be used only for failure reporting, so they provide a consistent way to report and respond to those failures. Exceptions are not intended to provide a mechanism to control program flow, to report success conditions, or to be used as a feedback mechanism. They are for reporting failures that occur during execution.

In many languages, particularly non-object-oriented languages, the standard mechanism for reporting failures is to use a return code. However, in an object-oriented language, return codes aren’t always possible, depending on the context in which the failure occurs. Return codes are also easily disregarded; when not, they introduce a lot of complexity to properly handle all the possible failure locations.

In this hour, you learn about the exception handling mechanisms provided by C#; you also learn how to throw and catch exceptions. You learn how to choose the right type of exception to throw, what exceptions are available to you, including how to create your own custom exceptions, and finish by looking at some performance considerations when using exceptions. Finally, you look at how exceptions and code contracts can be used for parameter validation.

Understanding Exceptions

Exceptions provide a clear, concise, and safe way to represent failures that occur during runtime and usually carry detailed information about the cause of the failure. Some of this information includes the call stack trace, which shows the execution path that would occur if the current block returned normally.

Exceptions are not meant to provide a way to handle anticipated errors, such as those that could come from user actions or input. In those cases, it is far better to prevent errors by validating the action or input for correctness. Exceptions are not protection against coding errors, either. They might be caused by coding errors, but those errors should be fixed rather than relying on exceptions.

An exception is handled when the application provides explicit code that is run when the exception occurs. Consequently, an unhandled exception occurs when no such code is found.

System.Exception

All exceptions derive from System.Exception. When managed code calls other unmanaged code or external services, such as Microsoft SQL Server, and an error condition occurs, the .NET runtime wraps that error condition in a System.Exception derived exception.


Tip: RuntimeWrappedException

Languages such as C++ enable you to throw exceptions of any type, not just ones derived from System.Exception. In those cases, the common language runtime wraps those exceptions in a RuntimeWrappedException. This maintains compatibility between languages.


System.Exception provides several properties that provide detailed information about the error that occurred:

• The most commonly used is the Message property, which provides the details about the cause of an exception in a user-readable description. Typically, the content of this property is a few sentences that describe the error in general terms.

• The StackTrace property contains the call stack trace, which can help determine where an error occurred. If debugging information is available during runtime, the stack trace includes the name of the source file and the line number where the error occurred.

• The InnerException property is typically used when one exception is wrapped in a new exception of a different type. The original exception is stored in the InnerException property so that error-handling code can examine the original information.

• The HelpLink property can contain a URL to a Help file containing additional detailed information about the exception.

• The Data property is an IDictionary (which is a nongeneric version of a dictionary) to hold arbitrary data in key/value pairs.

Using the Standard Exceptions

Although there are more than 200 public exception classes provided by the .NET Framework, approximately 15 of them are commonly used. The remaining exceptions generally derive from one of these standard exceptions.

The two primary base classes other than Exception are SystemException, which is the base class for almost all runtime-generated exceptions and ExternalException, which is for exceptions that occur in or are targeted at environments outside of the runtime.

The Exception, SystemException, and ExternalException classes should only be used as base classes for more specific derived exceptions, and you should avoid deriving your own exceptions directly from SystemException.

The remaining standard exceptions, shown in Table 11.1, make up a combination of exceptions thrown by the runtime, which your own code should not throw, and those that can, and should, be thrown from your own code.

Table 11.1. Standard Exceptions

Image

If you are diligent about validating the parameters passed to your public methods, you should throw ArgumentException, or one of its subtypes, when bad arguments are passed.

You should use ArgumentNullException for those methods that receive a null argument when it is not expected; use ArgumentOutOfRangeException when the argument falls outside the acceptable range of values.

If a property or method call is not appropriate for the current state of the object, you can throw an InvalidOperationException. This is different from ArgumentException, which does not rely on object state to determine if it should be thrown.


Note: Validating Arguments

If it isn’t straightforward for the caller to determine when an argument is valid, you should consider providing a method that allows it to check.


The remaining standard exceptions should be considered reserved exceptions. You should avoid throwing these from your own code and deriving your own exceptions from them.

You should perform argument checking to prevent an IndexOutOfRangeException or NullReferenceException from occurring.

Throwing Exceptions

You throw an exception using the throw keyword. Because Exception is a class, you must create an instance of it using the new keyword. The following code throws a new Exception object:

throw new System.Exception();

When an exception is thrown, program execution immediately halts while the exception propagates up the call stack, looking for an appropriate handler. If no handlers are found, one of three things happens:

• If the exception occurred within a destructor, that destructor is aborted, and if present, the destructor of the base class is called.

• If a static constructor or static field initializer is contained in the call stack, a TypeInitializationException is thrown containing the original exception in the InnerException property.

• If the start of the thread is reached, the thread is terminated. In most cases, this means that if the exception reaches all the way to the Main() method without finding a compatible handler, the application is terminated. This happens no matter which thread the exception originated from.

Knowing when an exception should be thrown requires understanding the difference between coding errors and execution errors. Coding errors can be avoided by changing your code, and there is no reason to handle these using exceptions. Coding errors can be fixed at compile time, and you can take steps to ensure they never occur at runtime.


Tip: System.Environment.FailFast

If your application encounters a situation in which it is unsafe to continue, you should consider calling System.Environment.FailFast instead of throwing an exception.

If continuing in such a situation causes a security risk, such as when a security impersonation cannot be reverted, you should also consider calling FailFast.


An exception is thrown when an unexpected error condition is encountered. This typically occurs when a class member cannot perform what it is designed to do. These execution errors cannot be completely avoided, no matter how many precautions you might take in the code. Program execution errors can be handled programmatically by your code. System execution errors are those that cannot be handled by your code.

Handling Exceptions

Handling exceptions uses exception objects and protected regions. Think of a protected region as a special block of code designed to enable you to work with exceptions. Almost any line of code can cause an exception, but most applications don’t actually need to deal with these exceptions. You should handle an exception only if there is something meaningful you can do as a result.

In C#, you declare a protected region, also known as a try block, using the try keyword, with the statements being protected enclosed in braces. The associated handlers appear after the closing brace of the protected region. A protected region must have associated with it at least one of the following handlers:

• A finally handler, which executes whenever the protected region exits, even if an exception was encountered. A protected region can have at most one finally handler.

• A catch handler that is compatible with a specific exception or any one of its subclasses. A protected region can have multiple catch handlers, but a specific exception type can only be specified in a single handler.

When an exception occurs, the first protected region that contains the current instruction (the one that caused the exception) and has a matching catch handler block are located. If the current method does not contain a match, the calling method is searched, continuing until either a match is found or the top of the call stack is reached, in which case the application terminates. If a match is found, program execution returns to the point of the exception, executes any finally handlers, and then the catch handler is executed.


Note: General Catch Handlers

You can also specify a catch handler using just the catch keyword, commonly known as a general catch handler. Doing so, however, should be avoided.

In the .NET Framework 1.0 and 1.1 releases, situations existed in which unmanaged code would throw an exception that wasn’t properly handled by the runtime. As a result, it wasn’t wrapped in a System.Exception derived exception and couldn’t be caught by anything other than an empty catch block.

This issue was corrected in .NET 2.0 and now these exceptions are wrapped in a RuntimeWrappedException (which inherits from System.Exception), so there is no longer a need for this empty catch block.


Protected regions can be broken down into three patterns, depending on the handlers they provide:

Try-Catch—Provides one or more catch handlers only

Try-Finally—Provides only a finally handler

Try-Catch-Finally—Provides one or more catch handlers and a finally handler

When you specify a catch block, it is possible to provide only the exception type or the exception type and an identifier, called the catch handler variable. The catch handler variable defines a local variable within the scope of the catch handler, enabling you to reference the exception object inside the catch block. Because the catch handler variable is scoped to that specific catch handler, and only one catch handler will execute, the same identifier can be used by multiple catch handlers. However, you cannot use the same identifier as any of the method parameters or other local variables.

Listing 11.1 shows the most common way to write a catch handler and uses a catch handler variable.

Listing 11.1. Declaring a Catch Handler


try
{
int divisor = Convert.ToInt32(Console.ReadLine());
int result = 3/divisor;
}
catch (DivideByZeroException ex)
{
Console.WriteLine(ex.Message);
}


If you have multiple nested try blocks, each with a possibly matching catch handler, the order in which they are nested determines the order in which they execute. When a compatible catch handler has been found, no other catch handlers are executed. Listing 11.2 shows an example of catching multiple exceptions and shows how to write a catch handler that does not use a catch handler variable.

Listing 11.2. Catching Multiple Exceptions


try
{
int divisor = Convert.ToInt32(Console.ReadLine());
int result = 3/divisor;
}
catch (DivideByZeroException)
{
Console.WriteLine("Attempted to divide by zero");
}
catch (FormatException)
{
Console.WriteLine("Input was not in the correct format");
}
catch (Exception)
{
Console.WriteLine("General catch handler");
}


If the try block in Listing 11.2 resulted in a DivideByZeroException, the output would be "Attempted to divide by zero". If the try block resulted in a FormatException, the output would be "Input was not in the correct format". Any other exceptions would generate "General catch handler" as the output. However, if the catch blocks were rearranged so the general catch block came first, the program would fail to compile.


Caution: Swallowing Exceptions

It is common to write catch blocks that do nothing more than log the error. Although this is sometimes important, it can usually be done by the top-level caller and does not need to occur for every function.

This leads to a problem known as “swallowing exceptions,” and occurs when you catch an exception and either do nothing with it or don’t allow it to pass up the chain. This can also lead to problems because you are effectively hiding the exception and not doing anything with it, which can lead to intermittent problems that will be hard to track.

The best way to look at this situation is that you should catch an exception only if you have meaningful cleanup work that needs to be done as a result of the exception (such as closing files or database connections).


Remember, a catch handler executes only if an exception has occurred, so it should not be used for cleanup activities. If you do need to perform any type of cleanup activities, they should be done in a finally handler. This means that unless you can legitimately take some action because of an exception, you should use the try-finally pattern instead of the try-catch or try-catch-finally patterns.


Try It Yourself: Working with Exceptions

To see how you can work with exceptions by throwing and catching several different exceptions, follow these steps:

1. Create a new console application.

2. In the Program.cs file, implement the following functions:

private static void ThrowsException()
{
Console.WriteLine("About to throw an InvalidOperationException");
throw new InvalidOperationException();
}

private static void PrintString(string message)
{
if (message == null)
{
throw new ArgumentNullException("message");
}

Console.WriteLine(message);
}

3. In the Main method of the Program.cs file, call the ThrowsException method.

4. Run the application using Ctrl+F5. You see the console window initially, as shown in Figure 11.1.

Image

Figure 11.1. Console application just before throwing an exception.

If you run Windows Vista or later, you might see the dialog box shown in Figure 11.2.

Image

Figure 11.2. Windows application failure dialog box checking for solutions.

Express for Desktops does not support JIT Debugging, so after this dialog box completes, you see the exception information displayed, as shown in Figure 11.3.

Image

Figure 11.3. Runtime exception generated from the lack of type safety.

The information displayed is the exception information and indicates that this was an unhandled exception of type System.InvalidOperationException. It also displays the stack trace information, which includes the method call stack, filename, and line numbers. This stack trace information helps you to identify the location in your code where the exception occurred.

If you have one of the Visual Studio with MSDN editions installed, you see a dialog box, as shown in Figure 11.4, enabling you to close or debug the application.

Image

Figure 11.4. Windows application failure dialog box closing the application.

5. If you press the Close program button, the console window should look like Figure 11.3. If you press the Debug button, you see the Visual Studio Just-In-Time Debugger dialog box.

6. Run the application again using Ctrl+F5, but this time press the Debug button when the dialog box shown in Figure 11.3 appears. This displays the Visual Studio Just-In-Time Debugger dialog box, as shown in Figure 11.5.

Image

Figure 11.5. Visual Studio Just-In-Time Debugger dialog box.

If you press the Yes button, control returns to Visual Studio with the line containing the exception set to the current statement and the Exception Assistant dialog box displayed, as shown in Figure 11.6.

Image

Figure 11.6. Exception Assistant.

By clicking the View Detail link under Actions, you can see the details of the exception, as shown in Figure 11.7.

Image

Figure 11.7. Exception details dialog box.

7. Enclose the call to ThrowsException in a try/catch block that specifically catches an InvalidOperationException assigning it to a catch handler variable named ex. In the body of the catch handler, print out the value of the Message property.

8. Run the application using Ctrl+F5. You see the console window shown in Figure 11.8.

Image

Figure 11.8. Handling the runtime exception.

Notice that this time, the application did not terminate, but instead displayed the message associated with the exception.

9. Change the body of Main to the following:

try
{
PrintString(null);
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.GetType().Name);
Console.WriteLine(ex.Message);
}
catch (ArgumentNullException ex)
{
Console.WriteLine(ex.GetType().Name);
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine(ex.GetType().Name);
Console.WriteLine(ex.Message);
}

10. Run the application using Ctrl+F5. You see the console window shown in Figure 11.9.

Image

Figure 11.9. Multiple catch handlers.

11. Now change the order of the catch handlers so that the catch(Exception) handler is first. You should notice that you immediately see two compiler errors indicating that the additional two catch handlers will not be executed because a previous handler catches the base exception type, as shown in Figure 11.10.

Image

Figure 11.10. Multiple catch handlers in the wrong order.


In general, you should avoid catching nonspecific exceptions, such as Exception, SystemException, and ExternalException, in your code. You should also avoid catching critical system exceptions, such as StackOverflowException or OutOfMemoryException. With the exceptions of these types, usually little can be done in response that is meaningful and catching them can hide runtime problems and complicate debugging and troubleshooting processes.


Note: Corrupted State Exceptions

Corrupted state exceptions are those exceptions that occur in a context outside of your application, such as the OS kernel, and indicate that the integrity of the running process might be in question. There are approximately 12 corrupted state exceptions, which are different from regular exceptions.

The difference between a regular exception and a corrupted state exception is not the type of exception, but the context in which it was thrown. By default, you cannot catch a corrupted state exception using a catch block, even if you catch Exception.


Rethrowing Caught Exceptions

If you do catch an exception for the purposes of logging or some other activity that does not actually handle the exception condition, you should rethrow the exception. Rethrowing an exception allows it to continue following the call stack, looking for an appropriate catch handler.

Just as the throw keyword enables you to specify a new exception to be thrown (as you saw in the previous section), it is also used to rethrow an exception.

To preserve the stack trace information, you should rethrow an exception using just the throw keyword, even if your catch handler included a catch handler variable.

A subtle difference exists between the two rethrow approaches shown in Listing 11.3 that won’t be apparent until you try to debug the problem. That difference is in the stack trace information sent with the exception.

Listing 11.3. Rethrowing Exceptions


try
{
// Some operation that results in an InvalidOperationException
}
catch (InvalidOperationException ex)
{
Console.WriteLine("Invalid operation occurred");
throw ex;
}
catch (Exception)
{
Console.WriteLine("General catch handler");
throw;
}


In the first approach, the catch handler for InvalidOperationException rethrows the exception using throw ex, which causes the stack trace to be truncated below the method that failed. This means that when you look at the stack trace, it looks as if the exception originated in your code. This isn’t always the case, particularly if you are bubbling up a CLR-generated exception (such as a SqlException).

This is a problem known as “breaking the stack,” because you no longer have the full stack trace information. This happens because, in essence, you are creating a new exception to throw.

By using throw by itself, as the catch handler for Exception does, you preserve the stack trace information. You can confirm this by looking at the IL generated for these two code blocks. This makes the difference obvious because in the first example, the IL instruction called is throw, whereas in the second, the instruction called is rethrow.

Wrapping Exceptions

You can also wrap a caught exception in a different exception and then throw that new exception. This is most commonly used when the actual exception provides no meaning or does not make sense in the context of a higher layer.

When you wrap an exception, you throw a new exception from your catch handler and should always include the original exception as the inner exception. Because the stack trace information is reset to the point of the new exception, including the inner exception is the only way to see the original stack trace and exception details. Listing 11.4 shows the correct way to throw a wrapped exception.

Listing 11.4. Throwing a Wrapped Exception


try
{
// Some operation that can fail
}
catch (Exception ex)
{
throw new InvalidOperationException("The operation failed", ex);
}


Wrapping exceptions should be a rare occurrence, and should be carefully considered. If you have any doubt about whether an exception should be wrapped, don’t wrap it. Wrapping exceptions can hide the method and location where the error actually occurred and can lead to a significant amount of time spent trying to debug the problem. It can also make it harder for the callers to handle the exception because now they not only have to handle the exception, but also your wrapped exception, and extract the real exception from it to handle it.

Overflow and Integer Arithmetic

In Hour 3, “Understanding C# Types,” you learned that all the primitive numeric data types have a fixed range. The minimum and maximum values of that range can be accessed through the MinValue and MaxValue properties, respectively. As a result, what would happen if you attempted to add 1 to int.MaxValue?

The answer to that question depends on the compiler settings used. The C# compiler defaults to generating code that silently enables the overflow to occur, wrapping the value to the largest negative value possible. In many cases, this behavior is acceptable because the risk of it occurring is low.

The risk is so low compared with the high frequency of integer arithmetic operations that occur that the overhead of performing overflow checking on each operation would cause performance considerations.

If you need to safeguard against silently overflowing integer arithmetic operations, or otherwise want to control the overflow-checking behavior, you can use the checked and unchecked keywords. These keywords provide explicit control because they override any compiler settings specified.


Caution: Checked and Unchecked

In a checked context, statements resulting in arithmetic overflow raise an exception. In an unchecked context, the overflow is ignored and the result is truncated. Only certain numeric calculations are affected by a checked or unchecked context:

• Explicit conversions between integral types

• Expressions using the following operators: ++ -- -(unary) + - * /


The checked or unchecked keywords can be applied to a statement block, in which all integer arithmetic directly inside the block is affected, as shown in Listing 11.5.

Listing 11.5. Checked and Unchecked Blocks


int max = int.MaxValue;
checked
{
int overflow = max++;
Console.WriteLine("The integer arithmetic resulted in an OverflowException.");
}

unchecked
{
int overflow = max++;
Console.WriteLine("The integer arithmetic resulted in a silent overflow.");
}


The keywords can also be applied to integer expressions, in which case the contained expression is evaluated in either a checked or an unchecked context. The contained expression must be enclosed by parentheses.

In Listing 11.6, line 2 always results in an OverflowException, whereas line 3 never results in an OverflowException. Line 4, however, depends on the compiler settings in place when the program was compiled.

Listing 11.6. Checked and Unchecked Expressions


1. int x = int.MaxValue;
2. int a = checked(x + 1);
3. int b = unchecked(x + 1);
4. int c = x + 1;


Exceptions, Code Contracts, and Parameter Validation

Whenever you write code, the implementation contains a set of assumptions about the inputs and outputs. Sometimes these assumptions are clearly defined by the business logic being implemented; however, they can also be implied, in which case they aren’t documented at all. If you look back at the PrintString method you wrote in step 2 of the Working With Exceptions Try It Yourself exercise, you will see that the beginning of the method includes an if statement, which throws an exception if the message parameter is null. This statement is there to help ensure the correctness of the running application by not allowing invalid input.

If you look at the code shown in Listing 11.7, you will see several of these if statements, commonly called guard statements. These guard statements implement the following requirements:

• The divisor must be greater than 0.

• The dividend must be greater than 0.

• The divisor must be greater than the dividend.

Listing 11.7. A Method Using Multiple Guard Statements


public int Calculate(int divisor, int dividend)
{
if (divisor <= 0)
{
throw new ArgumentOutOfRangeException("divisor");
}

if (dividend <= 0)
{
throw new ArgumentOutOfRangeException("dividend");
}

if (divisor <= dividend)
{
throw new ArgumentException("divisor");
}

return dividend / divisor;
}


As you can see, there are more guard statements than actual implementation, making it difficult to read the intent of the code. In addition, a potentially more serious problem exists. The logic used by the guard statements is inverted from that described by the requirements.

To help solve these problems, the .NET Framework includes a set of code contract classes, available in the System.Diagnostics.Contracts namespace. The class you will use the most is the Contract class, which contains static methods that represent the code contract concepts of preconditions, postconditions, and invariants.

If you consider the guard statements to describe a set of assumptions about the input, it is reasonable to say that these assumptions describe a contract that the method satisfies. You could further say that these assumptions are actually preconditions that must be satisfied before the Calculatemethod will execute.


Note: Design by Contract

Specifying contracts is part of an idea called Design by Contract pioneered by the Eiffel programming language in late 1986. Code contracts operate on the belief that classes and methods should explicitly state what conditions they require (preconditions) and what they guarantee if those conditions are met (postconditions).

In effect, code contracts provide a way to document the contract of the method or class in a way that is not only easy to read but also usable by other development tools. It is these other development tools which can include contract specifications in generated documentation, perform static (compile-time) contract verification, or even perform runtime contract checking.

Although the name Design by Contract is commonly used in languages other than Eiffel, it is a registered trademark of Eiffel Software. As a result, it is also referred to as Programming by Contract, Contract Programming, Contract-First development, or Code Contracts.


Listing 11.8 shows the same Calculate method using code contracts rather than guard statements. The intent described by the contracts is much clearer than that described by the guard statements. In addition, the logic used by the code contract statements matches the way it is described in the business logic.

Listing 11.8. A Method Using Code Contracts


public int Calculate(int divisor, int dividend)
{
Contract.Requires(divisor > 0);
Contract.Requires(divided > 0);
Contract.Requires(divisor > dividend);

return dividend / divisor;
}


Preconditions, Postconditions, and Invariants

Preconditions, such as the guard statements shown in Listing 11.8, help answer the question of what input is expected. They are contracts about the input when the method is invoked and are most commonly used to validate parameter values.

If you must throw a specific exception when a precondition fails, you can use the generic overload of Requires, as shown in Listing 11.9.

Listing 11.9. Using Contract.Requires<T>


public int Calculate(int divisor, int dividend)
{
Contract.Requires<ArgumentOutOfRangeException>(divisor > 0, "divisor");
Contract.Requires<ArgumentOutOfRangeException>(dividend > 0, "dividend");
Contract.Requires<ArgumentException>(divisor > dividend, "divisor");

return dividend / divisor;
}



Go To

HOUR 12, “UNDERSTANDING GENERICS,” for more information on generic methods.


If preconditions help answer the question of what input is expected, postconditions help answer the question of what output is expected. They are contracts about the state of a method when it terminates and are checked just prior to exiting the method. Unlike preconditions, in which all members used in the precondition must be at least as accessible as the method itself, postconditions may reference members with less visibility.

Most postconditions will be specified using one of the Contract.Ensures overloads, as shown in Listing 11.10.

Listing 11.10. Using Postconditions


public int Calculate(int divisor, int dividend)
{
Contract.Requires<ArgumentOutOfRangeException>(divisor > 0, "divisor");
Contract.Requires<ArgumentOutOfRangeException>(dividend > 0, "divisor");
Contract.Requires<ArgumentException>(divisor > dividend, "divisor");
Contract.Ensures(result != 0);

int result = dividend / divisor;
return result;
}


In order to write this postcondition, it was necessary to change the implementation so the result of the division was captured in a local variable. Because this isn’t always practical, the code contracts library provides the Contract.Result<T> method to refer to the actual return value. UsingContract.Result<T> would allow the Calculate method to be written as shown in Listing 11.11.


Caution: Contract.Result<T> and void Methods

Methods that have a void return type cannot use Contract.Result<T> within their postconditions.


Listing 11.11. Using Postconditions and Contract.Result<T>


public int Calculate(int divisor, int dividend)
{
Contract.Requires<ArgumentOutOfRangeException>(divisor > 0, "divisor");
Contract.Requires<ArgumentOutOfRangeException>(dividend > 0, "dividend");
Contract.Requires<ArgumentException>(divisor > dividend, "divisor");
Contract.Ensures(Contract.Result<int>() != 0);

return dividend / divisor;
}


If you need to write a postcondition that references an out parameter, you must use the Contract.ValueAtReturn<T> method instead of Contract.Result<T>.

Just as you can refer to method return values and out parameters, it is also possible to refer to the original value of an expression from the precondition state using Contract.OldValue<T>. There are, however, several restrictions:

• The old expression may only be used in a postcondition.

• An old expression cannot refer to another old expression.

• The old expression must refer to a value that existed in the method’s prestate.


Note: Exceptional Postconditions

If you must ensure a particular postcondition when an exception occurs, you should use one of the Contract.EnsuresOnThrow<T> overloads, which specify the condition that must be true whenever an exception of type T (or one of its subtypes) is thrown.

Because using Contract.EnsuresOnThrow<T> requires the method to guarantee the condition when an exception is thrown, there are many exception types that make this difficult. For instance, if you used the type Exception for T, the method would need to guarantee the condition even if a stack overflow exception occurred. As a result, you should only use exceptional postconditions for those exceptions a caller could expect as part of the application programming interface (API).


Sometimes, it is necessary to specify a condition that should be true on each instance of a class. In other words, it defines a set of conditions that must be true both before and after an operation; therefore, invariants express the conditions that are required for the object to be in a “good” state.

Specifying invariants is done using an invariant method, which is simply a method that is marked with the ContractInvariantMethod attribute and contains only calls to the Contract.Invariant method, as shown in Listing 11.12.

Listing 11.12. Using Invariants


[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(this.result != 0);
}


While the name of the method is not meaningful, a best practice is to name it something meaningful like ObjectInvariants or just Invariants. It is also possible to specify more than one method for the object invariants, although you should probably keep it to just one method for ease of maintenance.


Note: Conditions and Purity

In addition, the condition should be free of side effects. This applies to any methods that are called within a contract as well. Such side-effect-free methods are also called pure methods and must not update any preexisting state; they can, however, modify objects created after the pure method has begun executing.

Currently, the following are assumed to be pure:

• Methods marked with the Pure attribute. When a type is marked with the Pure attribute, all methods in that type are considered to also be marked with the Pure attribute.

• Property get accessors.

• Operators.

• Any method whose fully qualified name begins with System.Diagnostics.Contracts.Contract, System.String, System.IO.Path, or System.Type.

• Any invoked delegate if the delegate type itself is marked with the Pure attribute. The System.Predicate<T> and System.Comparison<T> delegates are considered pure.


Go To

HOUR 21, “PROGRAMMING WITH ATTRIBUTES,” for more information on attributes.



Summary

In this hour, you learned about exceptions and learned how to use them in your applications. Exceptions occur as the result of an unexpected error and can cause your application to crash if not handled correctly. It is important to catch exceptions only at a point where some meaningful action can be taken as a result. You saw how the C# compiler helps you to catch exceptions in the correct order, catching the most derived exceptions first. You then learned how to perform integer arithmetic in a checked and unchecked manner, which helps prevent integer overflow from occurring. Finally, you learned how exceptions, and specifically code contracts, can be used to perform parameter validation to help ensure your application executes correctly.

Q&A

Q. When should you use exceptions?

A. Exceptions provide a clear, concise, secure, and safe way to represent failures that occur during runtime and should be used when an unexpected error condition is encountered, typically when a class member cannot perform what it is designed to do.

Q. What is an unhandled exception?

A. An unhandled exception is one that occurs when the application has not provided explicit code to execute when that exception occurs.

Q. What is a try-catch block?

A. A try-catch block is a protected region that includes one or more exception handlers.

Q. What is a try-finally block?

A. A try-finally block is a protected region that includes no exception handlers and executes whenever the try block exits, even if an exception occurs.

Workshop

Quiz

1. What is the base class for all exceptions?

2. What is a RuntimeWrappedException?

3. Should ArgumentException (or any of its subclasses) or InvalidOperationException be handled programmatically?

4. What is a corrupted state exception?

5. What happens if an exception is thrown and a static constructor or static field initializer is contained in the call stack?

6. When should you handle exceptions?

Answers

1. The base class for all exceptions is System.Exception.

2. When another language, such as C++, throws an exception that does not derive from System.Exception, that exception is wrapped in a RuntimeWrappedException to maintain compatibility between languages.

3. Typically ArgumentException, any of its subclasses, and InvalidOperationException represent coding errors and are best corrected at compile time rather than handling them programmatically in your application.

4. A corrupted state exception is one that occurs in a context outside of your application and indicates that the integrity of the running process might be in question. By default, it is not possible to catch a corrupted state exception.

5. If a static constructor or static field initializer is contained in the call stack, a TypeInitializationException is thrown containing the original exception in the InnerException property.

6. In general, you should handle an exception only at the closest point to where you can perform some meaningful actions because of the exception. As a result, you should use the try-finally pattern more often than the try-catch or try-catch-finally patterns.

Exercises

1. Add the following code to the beginning of the Photo(Uri) constructor in the Photo class of the PhotoViewer project:

System.Diagnostics.Contracts.Contract.Requires<ArgumentNullException>(path != null);

Wrap the existing if statement in a try-catch block. Use the following code for the catch(InvalidOperationException) handler:

this.source = null;
this.Refresh();

2. Modify the ToString method so that if this.source is null, an empty string is returned.