本章概要:
1:为什么需要异常
2:finally
3:什么时候需要捕获异常
4:什么时候需要抛出异常
5:异常处理中的错误做法
6:为什么需要自定义异常
7:自定义异常的设计要素
8:什么时候不需要自定义异常
1:为什么需要异常
利用异常处理,我们可以将资源清理代码放在一个固定的位置,并且确保它们得到执行。
利用异常处理,我们可以将异常处理的代码放在一个集中的位置。
异常处理的强制性,和前一个好处密切相关的可能是异常处理所带来的最大好处。
利用异常处理,我们可以很容易定位和修复代码中的bug。
2:finally
即使发生异常也会执行 finally 块中的代码。使用 finally 块释放资源,例如,关闭在 try 块中打开的任何流或文件。
在 finally 块中关闭数据库连接是另一个不错的选择。 因为有时候数据库服务器允许的连接数是有限的,所以应尽快关闭数据库连接。 在由于引发了异常而无法关闭连接的情况下,使用 finally 块也是比等待垃圾回收更好的一种选择。
3:什么时候需要捕获异常
当下列条件为真时,应该捕捉异常:
a、对引发异常的原因有具体的了解,并可实现特定的恢复,例如,捕捉 FileNotFoundException 对象并提示用户输入新的文件名。
b、可以新建一个更具体的异常并引发该异常。例如:
int GetInt(int[] array, int index)
{
try
{
return array[index];
}
catch(System.IndexOutOfRangeException e)
{
throw new System.ArgumentOutOfRangeException(
"Parameter index is out of range.");
}
}
c、部分处理异常。例如,可以使用 catch 块向错误日志中添加项,但随后重新引发该异常,以便对该异常进行后续处理。例如:
try
{
// try to access a resource
}
catch (System.UnauthorizedAccessException e)
{
LogError(e); // call a custom error logging procedure
throw e; // re-throw the error
}
4:什么时候需要抛出异常
a、方法无法完成其中定义的功能。
例如,如果方法的参数具有无效值:
static void CopyObject(SampleClass original)
{
if (original == null)
{
throw new System.ArgumentException("Parameter cannot be null", "original");
}
}
b、根据对象的状态,对某个对象进行不适当的调用。
一个示例可能尝试对只读文件执行写操作。在对象状态不允许某项操作的情况下,引发 InvalidOperationException 的一个实例或基于此类的派生类的对象。以下为引发 InvalidOperationException 对象的方法的示例:
class ProgramLog
{
System.IO.FileStream logFile = null;
void OpenLog(System.IO.FileInfo fileName, System.IO.FileMode mode) {}
void WriteLog()
{
if (!this.logFile.CanWrite)
{
throw new System.InvalidOperationException("Logfile cannot be read-only");
}
// Else write data to the log and return.
}
}
c、方法的参数导致了异常。
在此情况下,应捕获原始异常并创建一个 ArgumentException 实例。原始异常应作为 InnerException 参数传递给ArgumentException 的构造函数:
static int GetValueFromArray(int[] array, int index)
{
try
{
return array[index];
}
catch (System.IndexOutOfRangeException ex)
{
System.ArgumentException argEx = new System.ArgumentException("Index is out of range", "index", ex);
throw argEx;
}
}
5:异常处理中的错误做法
不要通过在框架代码中捕捉非特定异常(如 System.Exception、System.SystemException 等)来处理错误。
如果捕捉异常是为了再次引发或传输给其他线程,则可以捕捉这些异常。
不要显式引发 System.StackOverflowException。此异常仅应由公共语言运行时 (CLR) 显式引发。
不要捕捉 System.StackOverflowException。以编程方式处理堆栈溢出极为困难。应允许此异常终止进程并使用调试确定问题的根源。
不要显式引发 System.OutOfMemoryException。此异常仅应由 CLR 基础结构引发。
不要显式引发 System.Runtime.InteropServices.COMException 或 System.Runtime.InteropServices.SEHException。这些异常仅应由 CLR 基础结构引发。
不要显式捕捉 System.Runtime.InteropServices.SEHException。
不要显式引发 System.ExecutionEngineException。
6:为什么需要自定义异常
如果要对某类程序出错信息作出特殊处理了,那就自定义异常。
方便调试,通过抛出一个我们自己定义的异常类型实例,我们可以使捕获代码精确地知道所发生的事情,并以合适的方式进行恢复。
逻辑包装,自定义异常可包装多个其它异常,然后抛出一个业务异常。
方便调用者编码,在编写自己的类库或者业务层代码的时候,自定义异常可以让调用方更方便处理业务异常逻辑。如保存数据失败,可以分成两个异常“数据库连接失败。”、“网络异常。”
引入新异常类,使程序员能够根据异常类在代码中采取不同的操作。
7:自定义异常的设计要素
异常类名称一定要以后缀 Exception 结尾。
一致的命名约定有助于降低新库的学习曲线。
应使异常可序列化。异常必须可序列化才能跨越应用程序域和远程处理边界正确工作。
一定要在所有异常上都提供(至少是这样)下列常见构造函数。确保参数的名称和类型与在下面的代码示例中使用的那些相同。
public class NewException : BaseException, ISerializable
{
public NewException()
{
// Add implementation.
}
public NewException(string message)
{
// Add implementation.
}
public NewException(string message, Exception inner)
{
// Add implementation.
}
// This constructor is needed for serialization.
protected NewException(SerializationInfo info, StreamingContext context)
{
// Add implementation.
}
}
8:什么时候不需要自定义异常
考虑引发 System 命名空间中的现有异常,而不是创建自定义异常类型。
如果错误状态可以通过不同于现有任何其他异常的方法以编程方式进行处理,则要创建并引发自定义异常。否则,引发一个现有异常。
不要只是为了您所在的团队获得异常而创建和引发新异常。
练习:
1.You are working on a debug build of an application. You need to find the line of code that caused an exception to be thrown. Which property of the Exception class should you use to achieve this goal?
A. Data
B. Message
C. StackTrace
D. Source
Answer: C
2.You write the following custom exception class named CustomException.
public class CustomException:ApplicationException{
public static int COR_E_ARGUMENT=unchecked((int)0x80070075);
public CustomException(string msg):base(msg) {HResult=COR_E_ARGUMENT;}
}
You need to write a code segment that will use the CustomException class to immediately return control to the COM caller. You also need to ensure that the caller has access to the error code. Which code segment should you use?
A. return Marshal.GetExceptionForHR(CustomException.COR_E_ARGUMENT);
B. return CustomException.COR_E_ARGUMENT;
C. Marshal.ThrowExceptionForHR(CustomException.COR_E_ARGUMENT);
D. throw new CustomException("Argument is out of bounds");
Answer: D