Exception Handling in C#-.Net技术-3P代码网
繁体中文
设为首页
加入收藏
当前位置:.Net技术首页 >> Asp.Net开发 >> Exception Handling in C#

Exception Handling in C#

2007-07-15 08:00:00  作者:  来源:互联网  浏览次数:0  文字大小:【】【】【
简介:Exception Handling in C# Level Author Intermediate Anonymous What 抯 Wrong with Return Codes? Most programmers have probably written code that looked like this: bool success =CallFunction(); if (!...
关键字:C# Exception Handling in

Exception Handling in C#

Level Author

Intermediate Anonymous

What 抯 Wrong with Return Codes?

Most programmers have probably written code that looked like this:

bool success =CallFunction();

if (!success)

{

//process the error

}

This works okay, but every return value has to be checked for an error. If the above

was written as

CallFunction();

any error return would be thrown away. That抯 where bugs come from.

There are many different models for communicating status; some functions

may return an HRESULT , some may return a Boolean value, and others may use

some other mechanism.

In the .NET Runtime world, exceptions are the fundamental method of han-dling

error conditions. Exceptions are nicer than return codes because they can抰

be silently ignored.

Trying and Catching

To deal with exceptions, code needs to be organized a bit differently. The sections

of code that might throw exceptions are placed in a try block, and the code to handle

exceptions in the try block is placed in a catch block. Here抯 an example:

using System;

class Test

{

static int Zero =0;

public static void Main()

{

//watch for exceptions here

try

{

int j =22 /Zero;

}

//exceptions that occur in try are transferred here

catch (Exception e)

{

Console.WriteLine("Exception "+e.Message);

}

Console.WriteLine("After catch");

}

}

The try block encloses an expression that will generate an exception. In this case,

it will generate an exception known as DivideByZeroExceptio . When the division

takes place, the .NET Runtime stops executing code and searches for a try block

surrounding the code in which the exception took place. When it finds a try block,

it then looks for associated catch blocks.

If it finds catch blocks, it picks the best one (more on how it determines which

one is best in a minute), and executes the code within the catch block. The code in

the catch block may process the event or rethrow it.

The example code catches the exception and writes out the message that is

contained within the exception object.

The Exception Hierarchy

All C# exceptions derive from the class named Exception , which is part of the Common

Language Runtime 1 . When an exception occurs, the proper catch block is

determined by matching the type of the exception to the name of the exception

mentioned. A catch block with an exact match wins out over a more general

exception. Returning to the example:

using System;

class Test

{

static int Zero =0;

public static void Main()

{

try

{

int j =22 /Zero;

}

//catch a specific exception

catch (DivideByZeroException e)

{

Console.WriteLine("DivideByZero {0}",e);

}

//catch any remaining exceptions

catch (Exception e)

{

Console.WriteLine("Exception {0}",e);

}

}

}

The catch block that catches the DivideByZeroException is the more specific match,

and is therefore the one that is executed.

This example is a bit more complex:

using System;

class Test

{

static int Zero =0;

static void AFunction()

{

int j = 22 / Zero;

//the following line is never executed.

Console.WriteLine("In AFunction()");

}

public static void Main()

{

try

{

AFunction();

}

catch (DivideByZeroException e)

{

Console.WriteLine("DivideByZero {0}",e);

}

}

}

What happens here?

When the division is executed, an exception is generated. The runtime starts

searching for a try block in AFunction(), but it doesn抰 find one, so it jumps out of

AFunction(), and checks for a try in Main(). It finds one, and then looks for a catch

that matches. The catch block then executes.

Sometimes, there won抰 be any catch clauses that match.

using System;

class Test

{

static int Zero =0;

static void AFunction()

{

try

{

int j =22 /Zero;

}

//this exception doesn't match

catch (ArgumentOutOfRangeException e)

{

Console.WriteLine("OutOfRangeException:{0}",e);

}

Console.WriteLine("In AFunction()");

}

public static void Main()

{

try

{

AFunction();

}

//this exception doesn't match

catch (ArgumentException e)

{

Console.WriteLine("ArgumentException {0}",e);

}

}

}

Neither the catch block in AFunction()nor the catch block in Main()matches the

exception that抯 thrown. When this happens, the exception is caught by the "last

chance" exception handler. The action taken by this handler depends on how the

runtime is configured, but it will usually bring up a dialog box containing the excep-tion

information and halt the program.

Passing Exceptions on to the Caller

It抯 sometimes the case that there抯 not much that can be done when an exception

occurs; it really has to be handled by the calling function. There are three basic

ways to deal with this, which are named based on their result in the caller: Caller

Beware, Caller Confuse, and Caller Inform.

Caller Beware

The first way is to merely not catch the exception. This is sometimes the right design

decision, but it could leave the object in an incorrect state, causing problems when the

caller tries to use it later. It may also give insufficient information to the caller.

Caller Confuse

The second way is to catch the exception, do some cleanup, and then rethrow

the exception:

using System;

public class Summer

{

int sum =0;

int count =0;

float average;

public void DoAverage()

{

try

{

average =sum /count;

}

catch (DivideByZeroException e)

{

//do some cleanup here

throw e;

}

}

}

class Test

{

public static void Main()

{

Summer summer =new Summer();

try

{

summer.DoAverage();

}

catch (Exception e)

{

Console.WriteLine("Exception {0}",e);

}

}

}

This is usually the minimal bar for handling exceptions; an object should always

maintain a valid state after an exception.

This is called Caller Confuse because while the object is in a valid state after

the exception occurs, the caller often has little information to go on. In this case,

the exception information says that a DivideByZeroException occurred somewhere

in the called function, without giving any insight into the details of the exception

or how it might be fixed.

Sometimes this is okay if the exception passes back obvious information.

Caller Inform

In Caller Inform, additional information is returned for the user. The caught exception

is wrapped in an exception that has additional information.

using System;

public class Summer

{

int sum =0;

int count =0;

float average;

public void DoAverage()

{

try

{

average =sum /count;

}

catch (DivideByZeroException e)

{

//wrap exception in another one,

//adding additional context.

throw (new DivideByZeroException(

"Count is zero in DoAverage()",e));

}

}

}

public class Test

{

public static void Main()

{

Summer summer =new Summer();

try

{

summer.DoAverage();

}

catch (Exception e)

{

Console.WriteLine("Exception:{0}",e);

}

}

}

When the DivideByZeroException is caught in the DoAverage()function, it is wrapped

in a new exception that gives the user additional information about what caused the

exception. Usually the wrapper exception is the same type as the caught exception,

but this might change depending on the model presented to the caller.

This program generates the following output:

Exception:System.DivideByZeroException:Count is zero in DoAverage()--->

System.DivideByZeroException

at Summer.DoAverage()

at Summer.DoAverage()

at Test.Main()

Ideally, each function that wants to rethrow the exception will wrap it in an excep-tion

with additional contextual information.

User-Defined Exception Classes

One drawback of the last example is that the caller can抰 tell what exception hap-pened

in the call to DoAverage()by looking at the type of the exception. To know

that the exception was because the count was zero, the expression message would

have to be searched for the string is zero ".

That would be pretty bad, since the user wouldn抰 be able to trust that the text

would remain the same in later versions of the class, and the class writer wouldn抰

be able to change the text. In this case, a new exception class can be created.

using System;

public class CountIsZeroException:Exceptio

{

public CountIsZeroException()

{

}

public CountIsZeroException(string message)

:base(message)

{

}

public CountIsZeroException(string message,Exception inner)

:base(message,inner)

{

}

}

public class Summer

{

int sum =0;

int count =0;

float average;

public void DoAverage()

{

if (count ==0)

throw(new CountIsZeroException("Zero count in DoAverage"));

else

average =sum /count;

}

}

class Test

{

public static void Main()

{

Summer summer =new Summer();

try

{

summer.DoAverage();

}

catch (CountIsZeroException e)

{

Console.WriteLine("CountIsZeroException:{0}",e);

}

}

}

DoAverage()now determines whether there would be an exception (whether count

is zero), and if so, creates a CountIsZeroException and throws it.

Finally

Sometimes, when writing a function, there will be some cleanup that needs to be

done before the function completes, such as closing a file. If an exception occurs,

the cleanup could be skipped:

using System;

using System.IO;

class Processor

{

int count;

int sum;

public int average;

void CalculateAverage(int countAdd,int sumAdd)

{

count +=countAdd;

sum +=sumAdd;

average =sum /count;

}

public void ProcessFile()

{

FileStream f =new FileStream("data.txt",FileMode.Open);

try

{

StreamReader t =new StreamReader(f);

string line;

while ((line =t.ReadLine())!=null)

{

int count;

int sum;

count =Int32.FromString(line);

line =t.ReadLine();

sum =Int32.FromString(line);

CalculateAverage(count,sum);

}

f.Close();

}

//always executed before function exit,even if an

//exception was thrown in the try.

finally

{

f.Close();

}

}

}

class Test

{

public static void Main()

{

Processor processor =new Processor();

try

{

processor.ProcessFile();

}

catch (Exception e)

{

Console.WriteLine("Exception:{0}",e);

}

}

}

This example walks through a file, reading a count and sum from a file and using it

to accumulate an average. What happens, however, if the first count read from the

file is a zero?

If this happens, the division in CalculateAverage()will throw a DivideByZero-

Exception , which will interrupt the file-reading loop. If the programmer had

written the function without thinking about exceptions, the call to file.Close()

would have been skipped, and the file would have remained open.

The code inside the finally block is guaranteed to execute before the exit of

the function, whether there is an exception or not. By placing the file.Close()call

in the finally block, the file will always be closed.

Efficiency and Overhead

In languages without garbage collection, adding exception handling is expensive,

since all objects within a function must be tracked to make sure that they are

properly destroyed if an exception is thrown. The required tracking code both

adds execution time and code size to a function.

In C#, however, objects are tracked by the garbage collector rather than the

compiler, so exception handling is very inexpensive to implement and imposes little

runtime overhead on the program when the exceptional case doesn抰 occur.

Design Guidelines

Exceptions should be used to communicate exceptional conditions. Don抰 use them to

communicate events that are expected, such as reaching the end of a file. In the

normal operation of a class, there should be no exceptions thrown.

Conversely, don抰 use return values to communicate information that would

be better contained in an exception.

If there抯 a good predefined exception in the System namespace that describes

the exception condition梠ne that will make sense to the users of the class梪se

that one rather than defining a new exception class, and put specific information

in the message. If the user might want to differentiate one case from others where

that same exception might occur, then that would be a good place for a new excep-tion

class.

Finally, if code catches an exception that it isn抰 going to handle, consider whether

it should wrap that exception with additional information before rethrowing it.

责任编辑:admin
相关文章