c#编程
简介
软件程序员通过编写代码实现预期功能,但每个软件都可能因内部或外部原因未能执行其预期操作。C# 的异常处理系统允许程序员以结构化的方式处理错误或异常情况,从而将正常的代码流程与错误处理逻辑分离。
异常可以表示软件执行过程中出现的多种异常情况,这些异常可能是由内部或外部原因引起的:
- 外部原因:如网络连接失败、文件或系统资源访问权限不足、内存不足、或 Web 服务抛出的异常。这些通常是由应用程序依赖的外部环境(如操作系统、.NET 运行时、外部应用程序或组件)引起的。
- 内部原因:如软件缺陷、业务规则设计的功能性失败(如账户余额不足时禁止提现)、或由外部原因传播的异常(如运行时检测到的空对象引用、应用程序代码检测到的无效输入字符串)。
检测到错误条件的代码称为抛出异常,而处理这些错误的代码称为捕获异常。在 C# 中,异常是一个对象,封装了与错误相关的多种信息,如异常发生时的堆栈跟踪和描述性错误消息。所有异常对象都是 System.Exception
类或其子类的实例。
.NET Framework 定义了许多异常类用于各种用途,程序员也可以通过继承 System.Exception
或其他适当的异常类来自定义异常类。
在 .NET 2.0 之前,Microsoft 推荐开发人员的异常类继承自 ApplicationException
。但在 .NET 2.0 之后,这一建议被废弃,现在用户定义的异常类应直接继承自 Exception
类。
概述
C# 中有三种异常处理的代码定义:
try/catch
:执行代码,如果发生错误则捕获异常。try/catch/finally
:执行代码,捕获异常(如果发生),但无论是否发生异常都执行finally
块。try/finally
:执行代码,无论是否发生异常都执行finally
块。若有异常发生,会在finally
执行完成后抛出。
异常的捕获顺序: 异常按照从最具体到最不具体的顺序被捕获。例如,访问不存在的文件会触发以下顺序的异常处理:
FileNotFoundException
IOException
(FileNotFoundException
的基类)SystemException
(IOException
的基类)Exception
(SystemException
的基类)
如果抛出的异常未派生自捕获列表中的任何异常类,则会沿调用堆栈向上抛出。
示例
以下是几种不同类型异常的示例:
示例代码:try/catch
和 try/catch/finally
try/catch
示例
try/catch
语句用于尝试执行某些操作,如果发生错误,将控制转移到匹配的 catch
块。
示例 1:捕获单一异常
using System;
class ExceptionTest
{
public static void Main(string[] args)
{
try
{
// 尝试访问数组中的元素
Console.WriteLine(args[0]);
Console.WriteLine(args[1]);
Console.WriteLine(args[2]);
Console.WriteLine(args[3]);
Console.WriteLine(args[4]);
}
catch (ArgumentOutOfRangeException e)
{
// 捕获超出范围的异常
Console.WriteLine(e.Message);
}
}
}
示例 2:捕获多种异常
using System;
using System.IO;
class ExceptionTest
{
public static void Main(string[] args)
{
try
{
// 读取文件内容
string fileContents = new StreamReader(@"C:\log.txt").ReadToEnd();
}
catch (UnauthorizedAccessException e) // 文件访问权限问题
{
Console.WriteLine(e.Message);
}
catch (FileNotFoundException e) // 文件不存在
{
Console.WriteLine(e.Message);
}
catch (IOException e) // 其他 IO 问题
{
Console.WriteLine(e.Message);
}
}
}
示例 3:省略异常类型和变量名
在 catch
块中可以省略异常的类型和变量名:
try
{
int number = 1 / 0; // 除以零异常
}
catch (DivideByZeroException)
{
Console.WriteLine("Cannot divide by zero.");
}
catch
{
Console.WriteLine("An unspecified exception occurred.");
}
try/catch/finally
示例
当异常可能使程序处于无效状态时,可以使用 finally
块来确保某些操作(如释放资源)始终被执行,无论是否发生异常。
示例:处理数据库连接
using System;
using System.Data;
using System.Data.SqlClient;
class ExceptionTest
{
public static void Main(string[] args)
{
SqlConnection sqlConn = null;
try
{
// 创建数据库连接
sqlConn = new SqlConnection(/* 连接字符串 */);
sqlConn.Open();
// 执行数据库操作
Console.WriteLine("Database operations executed.");
}
catch (SqlException e)
{
// 捕获 SQL 异常
Console.WriteLine($"SQL Error: {e.Message}");
}
finally
{
// 确保连接被正确释放
if (sqlConn != null && sqlConn.State != ConnectionState.Closed)
{
sqlConn.Dispose();
Console.WriteLine("Database connection closed.");
}
}
}
}
说明:
- 在
try
块中打开数据库连接并进行操作。 - 如果发生异常,
catch
块会捕获并处理异常。 - 无论是否发生异常,
finally
块都会确保释放数据库连接资源。
finally
块用于确保清理操作(如关闭文件、释放内存或关闭数据库连接)始终得以执行,从而避免资源泄漏或不一致的状态。
第二个示例:异常处理代码
以下代码展示了一个简单的异常处理示例,其中包括多个 catch
块、finally
块,以及如何正确地捕获和处理异常。
代码示例 1:捕获多个异常并使用 finally
using System;
public class Excepation
{
public double num1, num2, result;
public void Add()
{
try
{
Console.WriteLine("Enter your numbers:");
num1 = Convert.ToInt32(Console.ReadLine());
num2 = Convert.ToInt32(Console.ReadLine());
result = num1 / num2; // 可能触发 DivideByZeroException
}
catch (DivideByZeroException e)
{
Console.WriteLine("Error: {0}", e.Message); // 捕获除以零异常
}
catch (FormatException ex)
{
Console.WriteLine("Error: {0}", ex.Message); // 捕获格式错误异常
}
finally
{
Console.WriteLine("Operation complete.");
}
}
public void Display()
{
Console.WriteLine("The Result is: {0}", result);
}
public static void Main()
{
Excepation ex = new Excepation();
ex.Add();
ex.Display();
}
}
说明:
try
块尝试从用户输入获取两个数字并执行除法。catch
块分别捕获DivideByZeroException
和FormatException
。finally
块始终会执行,用于打印操作完成信息。
代码示例 2:try/finally
try/finally
的主要用途是确保资源释放或其他清理操作。以下示例展示如何管理数据库连接:
using System;
using System.Data;
using System.Data.SqlClient;
class ExceptionTest
{
public static void Main(string[] args)
{
SqlConnection sqlConn = null;
try
{
sqlConn = new SqlConnection(/* Connection string */);
sqlConn.Open();
// 执行数据库操作
}
finally
{
if (sqlConn != null && sqlConn.State != ConnectionState.Closed)
{
sqlConn.Dispose(); // 确保连接被释放
}
}
}
}
说明:
- 即使
try
块中发生异常,finally
块仍会确保连接被正确关闭。 - 不在
try
块中声明资源,以便finally
块可以访问。
重新抛出异常
有时,抛出异常更有意义,例如:
- 异常超出了预期范围。
- 需要向异常中添加额外信息以帮助诊断。
错误示例:不要使用 throw ex
try
{
// 执行操作
}
catch (Exception ex)
{
throw ex; // 不推荐
}
推荐示例:使用 throw
保留原始异常信息
try
{
string value = ConfigurationManager.AppSettings["Timeout"];
if (value == null)
throw new ConfigurationErrorsException("Timeout value is not in the configuration file.");
}
catch (Exception ex)
{
throw; // 保留原始异常信息并向上抛出
}
添加额外信息到异常中
有时,可以通过嵌套异常添加额外信息。以下示例展示如何将现有异常作为 InnerException
传递:
public OrderItem LoadItem(string itemNumber)
{
DataTable dt = null;
try
{
if (itemNumber == null)
throw new ArgumentNullException("Item number cannot be null", "itemNumber");
dt = DataAccess.OrderItem.Load(itemNumber);
if (dt.Rows.Count == 0)
return null;
else if (dt.Rows.Count > 1)
throw new DuplicateDataException("Multiple items map to this item.", itemNumber, dt);
OrderItem item = OrderItem.CreateInstanceFromDataRow(dt.Rows[0]);
if (item == null)
throw new ErrorLoadingException("Error loading item " + itemNumber, itemNumber, dt.Rows[0]);
}
catch (DuplicateDataException dde)
{
throw new ErrorLoadingException("OrderItem.LoadItem failed with item " + itemNumber, dde);
}
catch (Exception ex)
{
throw; // 其他异常直接向上抛出
}
}
说明:
- 捕获
DuplicateDataException
并将其嵌套到ErrorLoadingException
中。 - 对于未预期的异常,直接重新抛出。
总结
- 始终捕获预期的异常,不要捕获和忽略所有异常。
- 使用
finally
确保资源被正确释放。 - 使用
throw
保留原始异常信息。 - 在需要时,嵌套异常并添加额外信息以帮助诊断问题。