详解C#编程中异常的创建和引发以及异常处理

 更新时间:2020年6月25日 11:25  点击:2711

创建和引发异常
异常用于指示在运行程序时发生了错误。此时将创建一个描述错误的异常对象,然后使用 throw 关键字“引发”该对象。然后运行时搜索最兼容的异常处理程序。
当存在下列一种或多种情况时,程序员应引发异常:
方法无法完成其中定义的功能。
例如,如果方法的参数具有无效值:

static void CopyObject(SampleClass original)
{
  if (original == null)
  {
    throw new System.ArgumentException("Parameter cannot be null", "original");
  }

}

根据对象的状态,对某个对象进行不适当的调用。
一个示例可能尝试对只读文件执行写操作。在对象状态不允许某项操作的情况下,引发 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.
  }
}

方法的参数导致了异常。
在此情况下,应捕获原始异常并创建一个 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;
  }
}

异常包含一个名为 StackTrace 的属性。此字符串包含当前调用堆栈上的方法的名称,以及为每个方法引发异常的位置(文件名和行号)。 StackTrace 对象由公共语言运行时 (CLR) 从 throw 语句点开始自动创建,因此必须从堆栈跟踪的开始点引发异常。
所有异常都包含一个名为 Message 的属性。应该设置此字符串来解释发生异常的原因。注意,不应将安全敏感信息放在消息文本中。除 Message 之外,ArgumentException 还包含一个名为 ParamName 的属性,应将该属性设置为导致引发异常的参数的名称。对于属性设置器,ParamName 应设置为 value。
公共的受保护方法应在其无法完成预期功能时引发异常。引发的异常类应该是符合错误条件的最确切的可用异常。这些异常应编写为类功能的一部分,派生类或对原始类的更新应保留相同的行为,以实现向后兼容性。
引发异常时要避免的情况
下表确定了在引发异常时要避免的做法:

  • 不应使用异常来更改正常执行过程中的程序流程。异常只能用于报告和处理错误条件。
  • 只能引发异常,而不能作为返回值或参数返回异常。
  • 不要从自己的源代码中有意引发 System.Exception、System.SystemException、System.NullReferenceException 或 System.IndexOutOfRangeException。
  • 不要创建可在调试模式下引发但不会在发布模式下引发的异常。若要在开发阶段确定运行时错误,请改用调试断言。

定义异常类

程序可以引发 System 命名空间中的预定义异常类(前面注明的情况除外),或通过从 Exception 派生来创建它们自己的异常类。派生类至少应定义四个构造函数:一个是默认构造函数,一个用来设置消息属性,一个用来设置 Message 属性和 InnerException 属性。第四个构造函数用于序列化异常。新异常类应该可序列化。例如:

public class InvalidDepartmentException : System.Exception
{
  public InvalidDepartmentException() : base() { }
  public InvalidDepartmentException(string message) : base(message) { }
  public InvalidDepartmentException(string message, System.Exception inner) : base(message, inner) { }

  // A constructor is needed for serialization when an
  // exception propagates from a remoting server to the client. 
  protected InvalidDepartmentException(System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context) { }
}

仅当新属性提供的数据有助于解决异常时,才应将其添加到异常类。如果向派生的异常类添加了新属性,则应重写 ToString() 以返回添加的信息。


异常处理
C# 程序员可使用 try 块对可能受异常影响的代码进行分区。关联的 catch 块用于处理任何结果异常。一个包含代码的 finally 块,无论 try 块中是否引发异常(例如,释放在 try 块中分配的资源),这些代码都会运行。一个 try 块需要一个或多个关联的 catch 块或一个 finally 块,或两者。
以下示例给出了一个 try-catch 语句,一个 try-finally 语句,和一个 try-catch-finally 语句。

 try
{
  // Code to try goes here.
}
catch (SomeSpecificException ex)
{
  // Code to handle the exception goes here.
  // Only catch exceptions that you know how to handle.
  // Never catch base class System.Exception without
  // rethrowing it at the end of the catch block.
}

 try
{
  // Code to try goes here.
}
finally
{
  // Code to execute after the try block goes here.
}

 try
{
  // Code to try goes here.
}
catch (SomeSpecificException ex)
{
  // Code to handle the exception goes here.
}
finally
{
  // Code to execute after the try (and possibly catch) blocks 
  // goes here.
}

不带有 catch 或 finally 块的 try 块将导致编译器错误。
Catch 块
catch 块可以指定要捕捉的异常的该类型。类型规范称为“异常筛选器”。异常类型应从 Exception 派生出来。一般而言,不会将 Exception 指定为异常筛选器,除非您了解如何处理 try 块中可能引发的所有异常,或者您在 catch 块中包括了 throw 语句。
具有不同异常筛选器的多个 catch 块可以串联在一起。多个 catch 数据块的计算顺序是在代码中从顶部到底部,但是,对于所引发的每个异常,都只执行一个 catch 数据块。与指定的准确类型或其基类最为匹配的第一个 catch 块被执行。如果 catch 块没有指定匹配异常筛选器,则 catch 块就不具有选定的筛选器(如果语句有的话)。需要将带有最具体的(即派生程度最高的)异常类的 catch 块放在最前面。
当下列条件为真时,应该捕捉异常:
对引发异常的原因有具体的了解,并可实现特定的恢复,例如,在捕获 FileNotFoundException 对象时提示用户输入新的文件名。
可以新建一个更具体的异常并引发该异常。

int GetInt(int[] array, int index)
{
  try
  {
    return array[index];
  }
  catch(System.IndexOutOfRangeException e)
  {
    throw new System.ArgumentOutOfRangeException(
      "Parameter index is out of range.");
  }
}

希望在将异常传递出去进行额外处理前部分地处理异常。在下面的示例中,catch 块用于在再次引发异常之前,向错误日志添加条目。

try
{
  // Try to access a resource.
}
catch (System.UnauthorizedAccessException e)
{
  // Call a custom error logging procedure.
  LogError(e);
  // Re-throw the error.
  throw;   
}

Finally 块
可以使用 finally 块清理在 try 块中执行的操作。如果存在,finally 块将在最后执行,在 try 块和任何匹配 catch 的块之后执行。不管是否引发异常或者是否找到与异常类型匹配的 catch 块,finally 始终运行。
可以使用 finally 块释放资源(如文件流、数据库连接和图形句柄),而不用等待由运行时中的垃圾回收器来完成对象。

在下面的示例中,使用 finally 块关闭在 try 块中打开的文件。注意,在关闭文件之前要检查该文件句柄的状态。如果 try 块无法打开文件,则文件句柄仍具有值 null,并且 finally 块不会尝试关闭它。或者,如果在 try 块中成功打开该文件,则 finally 块将关闭打开的文件。

System.IO.FileStream file = null;
System.IO.FileInfo fileinfo = new System.IO.FileInfo("C:\\file.txt");
try
{
  file = fileinfo.OpenWrite();
  file.WriteByte(0xF);
}
finally
{
  // Check for null because OpenWrite might have failed.
  if (file != null)
  {
    file.Close();
  }
}

[!--infotagslink--]

相关文章

  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25