C# 编程/异常
软件程序员编写代码以执行一些期望的动作。但是每个软件都可能由于内部或外部原因而无法执行其期望的动作。C# 语言中的异常处理系统允许程序员以结构化的方式处理错误或异常情况,允许程序员将代码的正常流程与错误处理逻辑分开。
异常可以表示软件执行期间发生的各种异常情况。这些条件可能是内部或外部造成的。执行失败的外部条件包括,例如,与远程组件连接的网络故障,使用文件/系统资源的权限不足,内存不足异常或 Web 服务抛出的异常等。这些主要是由于我们的应用程序所依赖的环境组件抛出的错误,例如操作系统、.NET 运行时或外部应用程序或组件。内部故障可能是由于软件缺陷、设计的功能故障(根据业务规则所需的故障)、传播的外部故障等,例如运行时系统检测到的空对象引用、用户输入的无效输入字符串并由应用程序代码检测到,或者用户要求提取超出账户余额的金额(业务规则)。
检测错误条件的代码被称为抛出异常,而处理错误的代码被称为捕获异常。C# 中的异常是一个对象,它封装了有关发生错误的各种信息,例如异常时的堆栈跟踪和描述性错误消息。所有异常对象都是System.Exception
类的实例,或者它的子类。.NET Framework 中定义了许多用于不同目的的异常类。程序员还可以定义自己的类,继承自System.Exception
或 .NET Framework 中其他合适的异常类。
微软在 2.0 版本之前推荐开发者异常类应该继承自ApplicationException
异常类。2.0 版本发布后,该建议已过时,用户异常类现在应继承自Exception
类[1]。
有三个代码定义用于异常处理。这些是
try
/catch
- 做一些事情并捕获错误(如果发生)。try
/catch
/finally
- 做一些事情,如果发生错误,则捕获错误,但始终执行finally
。try
/finally
- 做一些事情,但始终执行finally
。任何发生的异常将在finally
之后抛出。
异常按从最具体到最不具体的顺序捕获。例如,如果您尝试访问一个不存在的文件,CLR 会按以下顺序查找异常处理程序
FileNotFoundException
IOException
(FileNotFoundException
的基类)SystemException
(IOException
的基类)Exception
(SystemException
的基类)
如果抛出的异常不是从要捕获的异常列表派生或不在该列表中,则它将被向上抛出调用堆栈。
以下是一些不同类型异常的示例
try
/catch
执行一个操作,如果发生错误,则将控制权转移到 catch 块,如果有有效的 catch 部分。
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);
}
}
}
以下是一个具有多个 catch 的示例
class ExceptionTest
{
public static void Main(string[] args)
{
try
{
string fileContents = new StreamReader(@"C:\log.txt").ReadToEnd();
}
catch (UnauthorizedAccessException e) // Access problems
{
Console.WriteLine(e.Message);
}
catch (FileNotFoundException e) // File does not exist
{
Console.WriteLine(e.Message);
}
catch (IOException e) // Some other IO problem.
{
Console.WriteLine(e.Message);
}
}
}
在所有catch
语句中,您可以省略异常类型和异常变量名
try
{
int number = 1/0;
}
catch (DivideByZeroException)
{
// DivideByZeroException
}
catch
{
// some other exception
}
捕获问题是一个好主意,但有时会导致您的程序处于无效状态。例如,如果您打开与数据库的连接,发生错误,并且您抛出异常。您将在哪里关闭连接?在 try 和 exception 块中?嗯,在执行关闭操作之前可能会出现问题。
因此,finally
语句允许您处理“在所有情况下执行此操作”的情况。请参见下面的示例
using System;
class ExceptionTest
{
public static void Main(string[] args)
{
SqlConnection sqlConn = null;
try
{
sqlConn = new SqlConnection ( /*Connection here*/ );
sqlConn.Open();
// Various DB things
// Notice you do not need to explicitly close the connection, as .Dispose() does this for you.
}
catch (SqlException e)
{
Console.WriteLine(e.Message);
}
finally
{
if (sqlConn != null && sqlConn.State != ConnectionState.Closed)
{
sqlConn.Dispose();
}
}
}
}
第二个示例
using System;
public class excepation
{
public double num1, num2,result;
public void add()
{
try
{
Console.WriteLine("enter your number");
num1 = Convert.ToInt32(Console.ReadLine());
num2 = Convert.ToInt32(Console.ReadLine());
result = num1/num2;
}
catch(DivideByZeroException e) //FormatException
{
Console.WriteLine("{0}",e.Message);
}
catch(FormatException ex)
{
Console.WriteLine("{0}",ex.Message);
}
finally
{
Console.WriteLine("turn over");
}
}
public void display()
{
Console.WriteLine("The Result is: {0}",result);
}
public static void Main()
{
excepation ex = new excepation();
ex.add();
ex.display();
}
}
请注意,SqlConnection 对象是在try
/catch
/finally
之外声明的。原因是finally
无法看到在try
/catch
中声明的任何内容。通过在前面的作用域中声明它,finally
块能够访问它。
try
/finally
块允许你执行与上面相同操作,但区别在于抛出的错误会由 catch 块(如果可能)处理,然后向上抛到调用栈。
class ExceptionTest
{
public static void Main(string[] args)
{
SqlConnection sqlConn = null;
try
{
SqlConnection sqlConn = new SqlConnection ( /*Connection here*/ );
sqlConn.Open();
// Various DB bits
}
finally
{
if (sqlConn != null && sqlConn.State != ConnectionState.Closed)
{
sqlConn.Dispose();
}
}
}
}
有时候,由于以下两个原因,最好将错误向上抛到调用栈。
- 这不是你预期的发生的事情。
- 你在异常中添加了额外的信息,以帮助诊断。
一些开发者会这样编写空的 try
/catch
语句
try
{
// Do something
}
catch (Exception ex)
{
// Ignore this here
}
不推荐这种方法。你正在吞掉错误并继续执行。如果这个异常是 OutOfMemoryException
或 NullReferenceException
,继续执行是不明智的。因此,你应该始终捕获你期望发生的异常,并将其他异常抛出。
以下是另一个捕获异常的错误示例
/* Read the config file, and return the integer value. If it does not exist, then this is a problem! */
try
{
string value = ConfigurationManager.AppSettings["Timeout"];
if (value == null)
throw new ConfigurationErrorsException("Timeout value is not in the configuration file.");
}
catch (Exception ex)
{
// Do nothing!
}
如你所见,ConfigurationErrorsException
将被 catch (Exception)
块捕获,但它被完全忽略了!这是糟糕的编程习惯,因为你正在忽略错误。
以下也是不好的做法
try
{
..
}
catch (Exception ex)
{
throw ex;
}
CLR 现在会认为 throw ex;
语句是问题的根源,而实际上问题出在 try 部分。因此,永远不要以这种方式重新抛出。
更好的方法是
/* Read the config file, and return the integer value. If it does not exist, then this is a problem! */
try
{
string value = ConfigurationManager.AppSettings["Timeout"];
if (value == null)
throw new ConfigurationErrorsException("Timeout value is not in the configuration file.");
}
catch (Exception ex )
{
throw; // <-- Throw the existing problem!
}
throw;
关键字表示保留异常信息并将其向上抛到调用栈。
另一种方法是在异常中提供额外信息(可能是局部变量信息)。在这种情况下,你将异常封装在另一个异常中。你通常使用尽可能具体地描述问题的异常,或者创建你自己的异常,如果你找不到足够具体的异常(或者如果你想要包含额外的信息)。
public OrderItem LoadItem(string itemNumber)
{
DataTable dt = null;
try
{
if (itemNumber == null)
throw new ArgumentNullException("Item Number cannot be null","itemNumber");
DataTable dt = DataAccess.OrderItem.Load(itemNumber);
if (dt.Rows == 0)
return null;
else if (dt.Rows > 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); // <-- Include dde (as the InnerException) parameter
}
catch (Exception ex)
{
throw; // <-- We aren't expecting any other problems, so throw them if they occur.
}
}