
C# ПІДРУЧНИКИ / c# / Apress - Accelerated C# 2005
.pdf
144 C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S
public double Magnitude { get {
return Math.Sqrt( Math.Pow(this.real, 2) + Math.Pow(this.imaginary, 2) );
}
}
public static implicit operator bool( Complex c ) { return (c.real != 0) || (c.imaginary != 0);
}
// Other methods omitted for clarity.
private double real; private double imaginary;
}
public class EntryPoint
{
static void Main() {
Complex cpx1 = new Complex( 1.0, 3.0 ); if( cpx1 ) {
Console.WriteLine( "cpx1 is true" ); } else {
Console.WriteLine( "cpx1 is false" );
}
Complex cpx2 = new Complex( 0, 0 );
Console.WriteLine( "cpx2 is {0}", cpx2 ? "true" : "false" );
}
}
The end result is the same with this example. Now, you may be wondering why you would ever want to implement operator true and operator false rather than just an implicit bool conversion operator. The answer lies in the fact of whether it is valid for your type to be converted to a bool type or not. With the latter form, where you implement the implicit conversion operator, the following statement would be valid:
bool f = cpx1;
This assignment would work because the compiler would find the implicit conversion operator at compile time and apply it. However, if you were extremely tired the night you coded this line and really meant to assign f from a completely different variable, it may be a long time before you find the bug. This is one example of how gratuitous use of implicit conversion operators can get you in trouble.
The rule of thumb is this: Provide only enough of what is necessary to get the job done and no more. If all you want is for your type—in this case, Complex—to participate in Boolean test expressions, only implement operator true and operator false. Do not implement the implicit bool conversion operator unless you have a real need for it. If you do happen to have a need for it, and thus implement the implicit bool conversion operator, you don’t have to implement operator true and operator false, because they would be redundant. If you do happen to provide all three, the compiler will go with the implicit conversion operator rather than operator true and operator false, because invoking one is not more efficient than the other, assuming you code them the same.

C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S |
145 |
Summary
In this chapter, I covered some useful guidelines for overloading operators, including unary, binary, and conversion operators. Operator overloading is one of the features that makes C# such a powerful and expressive .NET language. However, just because you can do something doesn’t mean that you should. Misuse of implicit conversion operators and improperly defined semantics in other operator overloads has proven time and time again to be the source of great user confusion (and that user could be the author of the type) as well as unintended behavior. When it comes to overloading operators, provide only enough of what is necessary, and don’t go counter to the general semantics of the various operators. Since the CLS doesn’t require overloaded operator support, not all .NET languages support overloaded operators. Therefore, it’s important to always provide explicitly named methods that provide the same functionality. Sometimes, those methods are already defined in system interfaces, such as IComparable or IComparable<T>. Never isolate functionality strictly within overloaded operators unless you’re 100% sure that your code will be consumed by
.NET languages that do support operator overloading.
In the next chapter, I’ll cover the intricacies and tricks with regards to creating exception-safe and exception-neutral code in the .NET Framework.


C H A P T E R 7
■ ■ ■
Exception Handling and
Exception Safety
The CLR contains strong support for exceptions. Exceptions can be created and thrown at a point where code execution cannot continue due to some exceptional condition (usually a method failure or an invalid state). Once thrown, the CLR begins the process of unwinding the execution stack iteratively frame by frame.1 As it does so, it cleans up any objects that are local to each stack frame. At some point, a frame on the stack could have an exception handler registered for the type of exception thrown. Once the CLR reaches that frame, it invokes the exception handler to remedy the situation. If the stack unwind finishes and a handler is not found for the exception thrown, then the unhandled exception event for the current application domain may be fired and the application could be aborted.
Writing exception-safe code is a difficult art to master. It would be a mistake to assume that the only tasks required when writing exception-safe code are simply throwing exceptions when an error occurs and catching them at some point. Such a view of exception-safe code is shortsighted and will lead you down a path of despair. Instead, exception-safe coding techniques are those with which you can guarantee the integrity of the system in the face of exceptions. When an exception is thrown, the runtime will iteratively unwind the stack while cleaning up. Your job as an exception-safe programmer is to structure your code in such a way that the integrity of the state of your objects is not compromised as the stack unwinds. That is the true essence of exception-safe coding techniques.
Handling Exceptions
Where should you handle exceptions? You can find the answer by applying a variant of the Expert pattern, which states that work should be done by the entity that is the expert with respect to that work. That is a circuitous way of saying that you should catch the exception at the point where you can actually handle it with some degree of knowledge available to remedy the exceptional situation. Sometimes, the catching entity could be close to the point of the exception generation within the stack frame. The code could catch the exception, then take some corrective action, and then allow the program to continue to execute normally. Other times, the only reasonable place to catch an exception is at the entry-point Main method, at which point you could either abort the process after providing some useful data, or you could reset the process as if the application were just restarted.
1.If you’re not familiar with the term stack frame, you may want to reference http://en.wikipedia.org/wiki/ Stack_frame. In short, as each method is called throughout the execution of a program, a frame is built on the stack that contains the passed parameters and any local parameters to the method. The frame is deleted upon return from the method. However, as the method calls other methods, and so on, new frames are stacked on top of the current frame, thus implementing a nested call-frame structure.
147

148C H A P T E R 7 ■ E X C E P T I O N H A N D L I N G A N D E X C E P T I O N S A F E T Y
The bottom line is that you should figure out the best way to recover from exceptions, if that is possible, and the best place to do so based upon where it makes the most sense to do it.
Avoid Using Exceptions to Control Flow
It can be tempting to use exceptions to manage the flow of execution in complex methods. This is never a good idea, for a couple of reasons. First, exceptions are generally expensive to generate and handle. Therefore, if you were to use them to control execution flow within a method that is at the heart of your application, your performance will likely degrade. Second, it trivializes the exceptional nature of exceptions in the first place. The whole point of exceptions is to indicate an exceptional condition in a way that can be handled or reported cleanly.
Historically, programmers have been rather lazy when it comes to handling error conditions. How many times have you seen code where the programmer never even bothered to check the return value of an API function or a method call? Such lackadaisical approaches to error handling can lead to headaches in a hurry. Exceptions provide a syntactically succinct way to indicate and handle error conditions without littering your code with a plethora of if blocks and other traditional (nonexceptionbased) error-handling constructs. At the same time, the runtime supports exceptions, and it does a lot of work on your behalf when exceptions are thrown. Unwinding the stack is no trivial task in and of itself. Lastly, the point where an exception is thrown and the point where it’s handled can be disjoint and have no connection to each other. Thus, it can be difficult when reading code to determine where an exception will get caught and handled. These reasons alone are enough for you to stick to traditional techniques when managing normal execution flow.
■Note You can find an article, “The Cost of Exceptions,” on Rico Mariani’s blog at http://blogs.msdn.com/ ricom/archive/2003/12/19/44697.aspx. Rico is an expert regarding performance-related issues in the CLR.
Mechanics of Handling Exceptions in C#
If you’ve ever used exceptions in other C-style languages such as C++, Java, or even C/C++ using the Microsoft structured exception-handling extensions (__try/__catch/__finally), then you’re already familiar with the basic syntax of exceptions in C#. In that case, you may find yourself skimming the next few sections or treating the material as a refresher. I’ve tried to point out any areas that are significantly different from the other C-style languages in the process.
Throwing Exceptions
The act of throwing an exception is actually quite easy. You simply execute a throw statement where the parameter to the throw statement is the exception you would like to throw. For example, suppose you have written a custom collection class that allows users to access items by index, and you would like to notify users when an invalid index is passed as a parameter. You could throw an ArgumentOutOfRange exception, such as in the following code:
public class MyCollection
{
public object GetItem( int index ) { if( index < 0 || index >= count ) {
throw new ArgumentOutOfRangeException();
}

C H A P T E R 7 ■ E X C E P T I O N H A N D L I N G A N D E X C E P T I O N S A F E T Y |
149 |
// Do other useful stuff here
}
private int count;
}
The runtime can also throw exceptions as a side effect to code execution. An example of a systemgenerated exception is NullReferenceException, which occurs if you attempt to access a field or call a method on an object when, in fact, the reference to the object doesn’t exist.
Changes with Unhandled Exceptions in .NET 2.0
When an exception is thrown, the runtime begins to search up the stack for a matching catch block for the exception. As it walks up the execution stack, it unwinds the stack at the same time, cleaning up each frame along the way.
If the search ends in the last frame for the thread, and it still finds no handler for the exception, the exception is considered unhandled at that point. What happens next depends on what version of the .NET Framework your code uses.
You can install an unhandled exception filter by registering a delegate with AppDomain. UnhandledException. When an unhandled exception comes up through the stack, this delegate will get called and it will receive an instance of UnhandledExceptionEventArgs.
■Note The CLR translates unhandled exceptions passing through static constructors. I’ll cover that in more detail in the section titled “Exceptions Thrown in Static Constructors.”
In .NET 1.1, the CLR designers decided to swallow certain unhandled exceptions in the pursuit of greater stability. For example, if a finalizer throws an exception in .NET 1.1 instead of aborting the finalizer thread and the process, the exception is swallowed and not allowed to kill the finalizer thread or terminate the process. Similarly, if an unhandled exception percolates up in a thread other than the main thread, that thread is terminated without affecting the rest of the process. If the thread is a thread pool thread, it is treated similarly to the finalizer thread in that the exception is swallowed and the thread is returned to the pool. If an unhandled exception propagates up from the main thread, then it behaves as expected, and the process is either terminated or the JIT debugging dialog displays, asking the user what to do.
This behavior sounds good in concept, but in reality, it gives the opposite of the desired result. Instead of providing greater stability, systems become unstable because code runs in a nondeterministic state. For example, consider a finalizer that does some crucial work. Say, halfway through that work, an exception is thrown. The second half of the work in the finalizer never runs. Now, the system is in a potentially unstable, half-baked state. Everything continues to run normally, although the state of the system could be far from normal. In practice, this causes great instability because the sources of the errors are hard to find since the exceptions are swallowed.
.NET 2.0 solves this problem by the fact that any unhandled exception, except
AppDomainUnloadException and ThreadAbortException, causes the thread to terminate. It sounds rude, but in reality, this is the behavior you should want from an unhandled exception. After all, it’s an unhandled exception. Now that the thread terminates as expected, a big red flag is raised at the point of the exception that allows you to find the problem immediately and fix it. This is always a good thing. You always want errors to present themselves as soon as possible and never swallow exceptions and just let the system keep running as if everything were normal.

150 C H A P T E R 7 ■ E X C E P T I O N H A N D L I N G A N D E X C E P T I O N S A F E T Y
■Note If you really want the .NET 2.0 unhandled exception behavior to emulate the .NET 1.1 behavior, you can request that by adding the following option to the application’s configuration file:
<system>
<runtime>
<legacyUnhandledExceptionPolicy enabled="1"/> </runtime>
</system>
Syntax Overview of the try Statement
The code within a try block is guarded against exceptions such that, if an exception is thrown, the runtime searches for a suitable catch block to handle the exception. Whether a suitable catch block exists or not, if a finally block is provided, the finally block is always executed no matter how execution flow leaves the try block. Let’s look at an example of a C# try statement:
using System;
using System.Collections;
using System.Runtime.CompilerServices;
// Disable compiler warning: CS1058
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = false)]
public class Entrypoint
{
static void Main() { try {
ArrayList list = new ArrayList(); list.Add( 1 );
Console.WriteLine( "Item 10 = {0}", list[10] );
}
catch( ArgumentOutOfRangeException x ) { Console.WriteLine( "=== ArgumentOutOfRangeException"+
" Handler ===" ); Console.WriteLine( x );
Console.WriteLine( "=== ArgumentOutOfRangeException"+ " Handler ===\n\n" );
}
catch( Exception x ) {
Console.WriteLine( "=== Exception Handler ===" ); Console.WriteLine( x );
Console.WriteLine( "=== Exception Handler ===\n\n" );
}
catch {
Console.WriteLine( "=== Unexpected Exception" + " Handler ===" );
Console.WriteLine( "An exception I was not" + " expecting..." );
Console.WriteLine( "=== Unexpected Exception" + " Handler ===" );
}
finally {
Console.WriteLine( "Cleaning up..." );
}
}
}

C H A P T E R 7 ■ E X C E P T I O N H A N D L I N G A N D E X C E P T I O N S A F E T Y |
151 |
Once you see the code in the try block, you know it is destined to throw an ArgumentOutOfRange exception. Once the exception is thrown, the runtime begins searching for a suitable catch clause that is part of this try statement and matches the type of the exception as best as possible. Clearly, the first catch clause is the one that fits best. Therefore, the runtime will immediately begin executing the statements in the first catch block. Had I not been interested in the actual exception contents, I could have left off the declaration of the exception variable x in the catch clause and only declared the type. But in this case, I wanted to demonstrate that exception objects in C# produce a nice stack trace that can be useful during debugging. While generating the output for this chapter, I compiled the samples without debugging symbols turned on. However, if you turn on debugging symbols, you’ll notice that the stack trace also includes file and line numbers of the various levels in the stack.
The second catch clause will catch exceptions of the general Exception type. Should the code in the try block throw an exception derived from System.Exception other than ArgumentOutOfRangeException, then this catch block would handle it. In C#, multiple catch clauses associated with a single try block must be ordered such that more specific exception types are listed first. The C# compiler will simply not compile code where more general catch clauses are listed before more specific catch clauses. You can verify this by swapping the order of the first two catch clauses in the previous example.
In C#, every exception that you can possibly throw must derive from System.Exception. Since
I declared a catch clause that traps exceptions of type System.Exception specifically, what’s the story with the third and last catch clause? Even though it is impossible to throw an exception of any type not derived from System.Exception in the C# language, it is not impossible in the CLR. (For example, you can throw an exception of any type in C++.) Therefore, if you wrote ArrayList in a language that allows this, it’s possible that the code could throw a not-very-useful type of, say, System.Int32. It sounds strange, but it is possible. In this case, you can catch such an exception in C# by using
a catch clause with neither an explicit exception type nor variable. Unfortunately, there’s no easy way to know what type the thrown exception is. Also, a try statement can have, at most, one such general catch clause.
■Note With .NET 2.0, the situation regarding general catch clauses is a little different than .NET 1.1. It features a new attribute, RuntimeCompatibilityAttribute, that you can attach to your assembly. The C# and Visual Basic compilers that target .NET 2.0 apply this property by default. It tells the runtime to wrap exceptions that are not derived from System.Exception inside an exception of type RuntimeWrappedException, which is derived from System.Exception. This is handy, because it allows your C# code to access the thrown exception. Previously, you could not access the thrown exception, since it was caught by a general, parameterless catch clause. You can access the actual thrown exception type via the RuntimeWrappedException.WrappedException property. If your code contains a parameterless catch clause, the compiler emits a warning of type CS1058 by default, unless you disable the attribute as I did in the previous example.
And finally (no pun intended), there is the finally block. No matter how the try block is exited, whether it be by reaching the end point of the block or via an exception of a return statement, the finally block will always execute. If there is a suitable catch block in the same frame as the finally block, it will execute before the finally block. You can see this by looking at the output of the previous code example, which looks like the following:
=== ArgumentOutOfRangeException Handler ===
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
at System.Collections.ArrayList.get_Item(Int32 index) at Entrypoint.Main()

152 C H A P T E R 7 ■ E X C E P T I O N H A N D L I N G A N D E X C E P T I O N S A F E T Y
=== ArgumentOutOfRangeException Handler ===
Cleaning up...
Rethrowing Exceptions and Translating Exceptions
Within a particular stack frame, you may find it necessary to catch all exceptions or a specific subset of exceptions long enough to do some cleanup and then rethrow the exception in order to let it continue to propagate up the stack. To do this, you use the throw statement with no parameter:
using System;
using System.Collections;
public class Entrypoint
{
static void Main() { try {
try {
ArrayList list = new ArrayList(); list.Add( 1 );
Console.WriteLine( "Item 10 = {0}", list[10] );
}
catch( ArgumentOutOfRangeException ) { Console.WriteLine( "Do some useful work and" +
"then rethrow" );
//Rethrow caught exception.
throw;
}
finally {
Console.WriteLine( "Cleaning up..." );
}
}
catch {
Console.WriteLine( "Done" );
}
}
}
Note that any finally blocks associated with the exception frame that the catch block is associated with will execute before any higher-level exception handlers are executed. You can see this in the output from the previous code:
Do some useful work and then rethrow
Cleaning up...
Done
In the “Achieving Exception Neutrality” section, I introduce some techniques that can help you avoid having to catch an exception, do some cleanup, and then rethrow the exception. That sort of work flow is cumbersome, since you must be careful to rethrow the exception appropriately. If you accidentally forget to rethrow, things could get ugly, since you would not likely be remedying the exceptional situation. The techniques that I show you help you achieve a goal where the only place to introduce a catch block is at the point where correctional action can occur.

C H A P T E R 7 ■ E X C E P T I O N H A N D L I N G A N D E X C E P T I O N S A F E T Y |
153 |
Sometimes, you may find it necessary to “translate” an exception within an exception handler. In this case, you catch an exception of one type, but you throw an exception of a different, possibly more precise, type in the catch block for the next level of exception handlers to deal with. Consider the following example:
using System;
using System.Collections;
public class MyException : Exception
{
public MyException( String reason, Exception inner ) :base( reason, inner ) {
}
}
public class Entrypoint
{
static void Main() { try {
try {
ArrayList list = new ArrayList(); list.Add( 1 );
Console.WriteLine( "Item 10 = {0}", list[10] );
}
catch( ArgumentOutOfRangeException x ) { Console.WriteLine( "Do some useful work" +
" and then rethrow" ); throw new MyException( "I'd rather throw this",
x ) ;
}
finally {
Console.WriteLine( "Cleaning up..." );
}
}
catch( Exception x ) { Console.WriteLine( x ); Console.WriteLine( "Done" );
}
}
}
One special quality of the System.Exception type is its ability to contain an inner exception reference via the Exception.InnerException property. This way, when the new exception is thrown, you can preserve the chain of exceptions for the handlers that process them. I recommend you use this useful feature of the standard exception type of C# when you translate exceptions. The output from the previous code is as follows:
Do some useful work and then rethrow Cleaning up...
MyException: I'd rather throw this ---> System.ArgumentOutOfRangeException: Index was out of range.
Must be non-negative and less than the size of the collection. Parameter name: index
at System.Collections.ArrayList.get_Item(Int32 index) at Entrypoint.Main()
--- End of inner exception stack trace ---