C#异常处理

异常时在程序执行期间出现的问题。C#中的异常时对程序运行时出现的特殊情况的一种响应,比如尝试除以零。

异常提供了一种吧程序控制权从某个部分转移到另一个部分的方式。C#异常处理时建立在四个关键字之上:Try、catch、finally、throw

Try:一个try块表示了一个将被激活的特定的异常的代码块。后跟一个或多个catch块。

catch:程序通过异常处理程序捕获异常。catch关键字表示异常的捕获。

finally:finally块用于执行给定的语句,不管异常是否被抛出都会执行。例如,如果您打开一个文件,不管是否出现异常文件都要被关闭。

throw:当问题出现时,程序抛出一个异常。使用throw关键字来完成。

  • try 块(保护区): 这里放置的是核心程序语句。即那些你认为可能会在运行期间发生错误的代码(例如文件读写、网络请求、数学计算等)。
  • catch 块(处理区): 这里放置的是错误处理语句。当 try 块中的代码抛出异常时,程序流程会立即跳转到这里。你可以在这里记录错误日志、提示用户或进行补救措施。
  • finally 块(清理区): 无论是否发生异常,这里的代码都会执行。通常用于释放资源(如关闭文件流、断开数据库连接),确保系统资源不泄露。

C#中的异常类

C#异常是使用类来表示的。C#中的异常类主要是直接或间接地派生于System.Exception类。System.ApplicationException和System.SystemException类是派生于System.Exception类的异常类。

System.ApplicationException类支持由应用程序生成的异常。所以程序员定义的异常都应派生自该类。

System.SystemException类是所有预定义的系统异常的基类。

下表列出了一些派生自System.SystemException类的预定义的异常类:

异常类

描述

System.IO.IOException

处理 I/O 错误。

System.IndexOutOfRangeException

处理当方法指向超出范围的数组索引时生成的错误。

System.ArrayTypeMismatchException

处理当数组类型不匹配时生成的错误。

System.NullReferenceException

处理当依从一个空对象时生成的错误。

System.DivideByZeroException

处理当除以零时生成的错误。

System.InvalidCastException

处理在类型转换期间生成的错误。

System.OutOfMemoryException

处理空闲内存不足生成的错误。

System.StackOverflowException

处理栈溢出生成的错误。

异常处理

C#以try和catch块的形式提供了一种结构化的异常处理方案。使用这些块,把核心程序语句与错误处理语句分离开。

这些错误处理块是使用try、catch和finally关键字实现的。下面是一个当除以零时抛出异常的实例:

using System;
namespace ErrorHandlingApplication
{
    class DivNumbers
    {
        int result;
        DivNumbers()
        {
            result = 0;
        }
        public void division(int num1, int num2)
        {
            try
            {
                result = num1 / num2;
            }
            catch (DivideByZeroException e)
            {
                Console.WriteLine("Exception caught: {0}", e);
            }
            finally
            {
                Console.WriteLine("Result: {0}", result);
            }

        }
        static void Main(string[] args)
        {
            DivNumbers d = new DivNumbers();
            d.division(25, 0);
            Console.ReadKey();
        }
    }
}

看着个

try { //

--- 核心程序语句 ---

// 这里放正常的业务逻辑

int result = 10 / 0; // 可能会发生除零异常

Console.WriteLine("计算结果: " + result);

}

catch (DivideByZeroException ex)

{

// --- 错误处理语句 ---

// 专门处理除零错误的逻辑

Console.WriteLine("发生错误:除数不能为零。");

Console.WriteLine("错误详情: " + ex.Message);

}

catch (Exception ex)

{

// 处理其他未知异常

Console.WriteLine("发生了其他未知错误。");

}

finally

{

// --- 资源清理 ---

// 无论是否出错,都会执行

Console.WriteLine("程序执行完毕,资源已释放。");

}

创建用户自定义异常

您也可以定义自己的异常。用户自定义的异常类是派生自ApplicationException类。下面的实例演示了这点:

using System;
namespace UserDefinedException
{
   class TestTemperature
   {
      static void Main(string[] args)
      {
         Temperature temp = new Temperature();
         try
         {
            temp.showTemp();
         }
         catch(TempIsZeroException e)
         {
            Console.WriteLine("TempIsZeroException: {0}", e.Message);
         }
         Console.ReadKey();
      }
   }
}
public class TempIsZeroException: ApplicationException
{
   public TempIsZeroException(string message): base(message)
   {
   }
}
public class Temperature
{
   int temperature = 0;
   public void showTemp()
   {
      if(temperature == 0)
      {
         throw (new TempIsZeroException("Zero Temperature found"));
      }
      else
      {
         Console.WriteLine("Temperature: {0}", temperature);
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

TempIsZeroException: Zero Temperature found

抛出对象

如果异常是直接或间接派生自System.Exception类,您可以抛出一个对象。您可以在catch块中使用throw语句来抛出当前的对象,如下所示:

Catch(Exception e)
{
   ...
   Throw e
}

虽然这种分离机制很好,但在使用时请注意以下几点:

  1. 只捕获你能处理的异常: 不要随意捕获所有异常然后忽略它(即空的 catch 块),这会导致程序在出错时“静默失败”,难以排查问题。
  2. 具体的异常在前: 如果你有多个 catch 块,请将最具体的异常类型(如 DivideByZeroException)放在前面,将通用的异常类型(如 Exception)放在最后。
  3. 保留堆栈信息: 如果你在 catch 块中处理完错误后决定重新抛出异常,请使用 throw; 而不是 throw ex;,这样可以保留原始的错误堆栈信息,方便调试。
场景 代码写法 结果
主动报错 throw new Exception("..."); 创建并抛出新异常
保留现场 catch { ... throw; } 重新抛出原异常,保留堆栈信息
重置现场 catch { ... throw ex; } 重新抛出,丢失原始堆栈信息 (应避免)

何时应该使用 throw

根据最佳实践,你应该在以下情况引发异常:

  • 方法无法完成其定义的功能: 例如参数为 null 或格式错误(抛出 ArgumentException)。
  • 对象状态不支持当前操作: 例如尝试写入一个只读文件(抛出 InvalidOperationException)。
  • 不要将异常作为控制流程的常规手段(例如用异常来代替 if 判断循环结束),这会严重影响性能。

更多推荐