理解和使用异常
异常在程序由于程序控制以外的情况而不能正常执行时发生。
在代码中不要完全依赖于异常。异常会导致性能严重下降。永远不要将异常用作控制正常程序流的方法。
何时使用异常:
在程序执行过程中调用函数时可产生三种结果:正常执行、错误执行或不正常执行。每一类别都在下面进行了描述。 
- 正常执行 
函数可能正常执行并返回。某些函数将结果代码返回到调用方,该结果代码指示函数的结果。函数的可能结果代码被严格定义,它表示该函数可能结果的范围。该结果代码可指示成功或失败,甚至可以指示正常期望值范围内的特定类型的失败。例如,文件状态函数可返回一个指示文件不存在的代码。注意,不使用术语“错误代码”是因为一个结果代码表示许多期望结果中的一种结果。在程序中应予以处理。(用if语句来判断,并选择相应的流程)
- 错误执行 
调用方在将参数传递到函数的过程中发生某些错误或在不适当的上下文中调用函数。这种情况导致错误,在程序开发过程中应由断言来检测该错误。(有关断言的更多信息,请参见使用断言。) 在Release版中应予以处理。(应优化代码排除这种情况)
- 不正常执行 
不正常执行包括超出程序控制的情况(如内存不足或 I/O 错误)正在影响函数的结果的情况。不正常状况应通过捕捉与引发异常来处理。 
使用异常尤其适用于不正常执行。
断言语句指定在程序的某些特定点应为真的条件。如果该条件不为真,则断言失败,中断程序的执行,并显示“断言失败”对话框。
断言可以用于: 
- 捕捉逻辑错误:根据程序逻辑必须为真的条件上设置断言
- 检查某操作的结果(检查结果):对于测试快速直观检查时结果不明显的操作最有价值 
- 测试错误条件,这些错误条件应已处理:
处理异常的最佳做法:
设计良好的错误处理代码块集可使程序更可靠并且不容易崩溃,因为应用程序可处理这样的错误。下表包含有关处理异常的最佳做法的建议: 
- 知道何时设置 Try/Catch 块。例如,可以以编程方式检查可能发生的条件,而不使用异常处理。在其他情况下,使用异常处理捕捉错误条件是适当的。 
下面的示例使用 if 语句检查连接是否关闭。如果连接未关闭,可以使用此方法而不是引发异常。 
   if(conn.State != ConnectionState.Closed)
      conn.Close();
在下面的示例中,如果连接未关闭,则引发异常。 
   try {
     conn.Close();
   }
   catch(InvalidOperationException ex) {
     //Do something with the error or ignore it.
   }
所选择的方法依赖于预计事件发生的频率。如果事件确实是异常的并且是一个错误(如意外的文件尾),则使用异常处理比较好,因为正常情况下执行的代码更少。如果事件是例行发生的,使用编程方法检查错误比较好。在此情况下,如果发生异常,将需要更长的时间处理。 
- 在可潜在生成异常的代码周围使用 Try/Finally 块,并将 Catch 语句集中在一个位置。以这种方式,Try 语句生成异常,Finally 语句关闭或释放资源,而 Catch 语句从中心位置处理异常。 
- 始终按从最特定到最不特定的顺序对 Catch 块中的异常排序。此方法在将特定异常传递给更常规的 Catch 块之前处理该异常。 
- 以“Exception”这个词作为异常类名的结尾。例如: 
public class MyFileNotFoundException : ApplicationException {
}
- 当创建用户定义的异常时,必须确保异常的元数据对远程执行的代码可用,包括当异常跨应用程序域发生时。例如,假设应用程序域 A 创建应用程序域 B,后者执行引发异常代码。应用程序域 A 若想正确捕捉和处理异常,它必须能够找到包含应用程序域 B 引发的异常的程序集。如果包含应用程序域 B 引发的异常的程序集位于应用程序域 B 的应用程序基下,而不是位于应用程序域 A 的应用程序基下,则应用程序域 A 将无法找到异常,公共语言运行库将引发 FileNotFoundException。为避免此情况,可以两种方式部署包含异常信息的程序集: - 将程序集放在两个应用程序域共享的公共应用程序基中 
 
- 将程序集放在两个应用程序域共享的公共应用程序基中 
- 或 - 
- 如果两个应用程序域不共享一个公共应用程序基,则用强名称给包含异常信息的程序集签名并将其部署到全局程序集缓存中。 
- 在 C# 和 C++ 的托管扩展中创建您自己的异常类时,至少使用三个公共构造函数。有关示例,请参见使用用户定义的异常。 
- 在大多数情况下,使用预定义的异常类型。仅为编程方案定义新异常类型。引入新异常类,使程序员能够根据异常类在代码中采取不同的操作。 
- 不要从 Exception 基类派生用户定义的异常。对于大多数应用程序,从 ApplicationException 类派生自定义异常。 
- 在每个异常中都包含一个本地化描述字符串。当用户看到错误信息时,该信息从引发的异常的描述字符串派生,而不是从异常类派生。 
- 使用语法上正确的错误信息(包括结束标点符号)。在异常的描述字符串中,每个句子都应以句号结尾。 
- 为编程访问提供 Exception 属性。仅当存在附加信息有用的编程方案时,才在异常中包含附加信息(不包括描述字符串)。 
- 对非常常见的错误情况返回空。例如,如果没找到文件,File.Open 返回空;但如果文件被锁定,则引发异常。 
- 类的设计应使在正常使用中从不引发异常。例如,FileStream 类公开另一种确定是否已到达文件尾的方法。这避免了在读取超过文件尾时引发的异常。下面的示例显示如何读到文件尾。 
class FileRead {
    void Open() {
        FileStream stream = File.Open("myfile.txt", FileMode.Open);
        byte b;
        // ReadByte returns -1 at EOF.
        while ((b == stream.ReadByte()) != true) {
            // Do something.
        }
    }
}
- 如果根据对象的当前状态,属性集或方法调用不适当,则引发 InvalidOperationException。 
- 如果传递无效的参数,则引发 ArgumentException 或从 ArgumentException 派生的类。 
- 堆栈跟踪从引发异常的语句开始,到捕捉异常的 Catch 语句结束。当决定在何处放置 Throw 语句时需考虑这一点。 
- 使用异常生成器方法。类从其实现中的不同位置引发同一异常是常见的情况。为避免过多的代码,应使用帮助器方法创建异常并将其返回。例如: 
class File {
    string fileName;
    public byte[] Read(int bytes) {
        if (!ReadFile(handle, bytes))
            throw NewFileIOException();
    }
    FileException NewFileIOException() {
        string description = // Build localized string, including fileName.
        return new FileException(description);
     }
}
或者,使用异常的构造函数生成异常。这更适合全局异常类,如 ArgumentException。 
- 引发异常,而不是返回错误代码或 HRESULT。 
- 引发异常时清理中间结果。当异常从方法引发时,调用方应该能够假定没有副作用。 
Visual C++ 支持三种异常处理: 
- C++ 异常处理 
在 MFC 程序中,使用 C++ 异常处理,不要使用 SEH。 
- 结构化异常处理 
Windows 提供了自己的异常机制,称为 SEH。建议不要用于 C++ 或 MFC 编程。仅在非 MFC C 程序中使用 SEH。 
- MFC 异常处理宏 
不建议将MFC 异常处理宏用于新的编程,但仍支持它们以保持向后兼容性。
虽然可以,但不建议混合使用错误处理机制;例如,不要混合使用 C++ 异常和 SHE;不再使用MFC异常理宏。
 
                    
                     
                    
                 
                    
                 
                
            
         
 
         浙公网安备 33010602011771号
浙公网安备 33010602011771号