C# 语言之异常处理
错误和异常
在日常的开发中,由于各种不确定因素,代码编译或程序运行可能会出现各种错误,比如:编译错误、使用错误、程序错误、系统错误等。但是,并非所有错误都需要在代码中做异常处理,下面是一些可能在编码或运行时出现的错误以及相应的处理方法:
编译错误:编译错误表示在编码过程做编写了错误代码导致编译器编译不通过,通常编译器(vs)会用红色下划线标注,这类错误最明显,我们只需要根据编译器提示的错误信息去修改便可。
逻辑错误:逻辑错误表示代码编译正常,但是代码处理逻辑出现了问题,导致程序执行产生了不确定(错误)的结果。像这样的情况,我们可以重新修正逻辑代码去规避错误的出现。我们来看下面的例子:
我们想实现的例子是,在
Person
类中重写Equals
方法来比较不同的Person
实例是否相等。
public class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public override bool Equals(object obj)
{
Person p = (Person) obj;
return this.Name.Equals(p.Name);
}
}
public class Example
{
public static void Main()
{
Person p1 = new Person();
p1.Name = "John";
Person p2 = null;
Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
}
}
在上面的示例中,程序执行到 p1.Equals(p2)=> Person p = (Person) obj;
时会出 NullReferenceException
异常,因为 obj
为 null
对象。
我们知道了异常出现的原因,可以对上面的代码做出修改,以此来规避异常的出现:
public override bool Equals(object obj)
{
Person p = obj as Person;
if (p == null)
return false;
else
return this.Name.Equals(p.Name);
}
程序错误:程序错误与上面的逻辑错误类似,都是在程序运行时产生的异常以及一些无法预料的结果。例如打开一个不存在的文件
File.Open("XXX", FileMode.Open);
当我们不确定文件是否存在时,我们应该先判断文件是否存在,如果存在才执行打开操作:File.Exists("XXX");
。还有在日常开发最容易出现的数据类型转换等。系统错误:系统错误同样是一个在程序运行时出现,切无法通过修改代码的方式来规避的错误。 例如,
OutOfMemoryException
如果公共语言运行时无法分配更多内存,则程序中的任何方法执行都有可能引发该异常。
Try/catch 块
公共语言运行时提供的异常处理模型,该模型将程序代码和异常处理代码用 try
块和 catch
块分离。可以有一个或多个 catch
块,每个块用于处理特定类型的异常。
在实际使用中,通常将无法预知结果 或 可能出现异常的语句放在 try
中,称为 try块
;将处理 try块
中语句引发的异常的语句放入 catch
中,成为 catch块
。通俗点说就是使用 try
将正常的代码块包裹,在 catch
中编写异常处理逻辑。
try
{
// to do
}
catch(Exception ex)
{
// exception to do
}
如果try块
中的代码发生异常,系统会按照代码异常在应用程序中出现的顺序搜索关联的catch块
,直到找到对应的 catch块
。系统在找到处理异常的第一个块后停止搜索 catch块
。
如果系统无法找到对应的 catch块
,并且当前 try块
嵌套在当前调用的其他 try 块
中,则搜索与下一个封闭块关联的 catch块
,如果仍未找到异常的catch块
,则系统会在当前调用中搜索先前的嵌套级别。如果在当前调用中找不到异常的catch块
,则将在调用堆栈中向上传递异常。
常用的异常类
Exception:所有异常类的基类
SystemException:System 命名空间中所有其他异常类的基类。(建议:公共语言运行时引发的异常通常用此类)
ApplicationException:应用程序发生非致命错误时所引发的异常。(建议:应用程序自身引发的异常通常用此类)
与参数有关的异常类
ArgumentException:该类派生于
SystemException
,用于处理参数无效的异常,除了继承来的属性名,此类还提供了string
类型的属性ParamName
表示引发异常的参数名称。FormatException:该类派生于
SystemException
,用于处理参数格式错误的异常。
与成员访问有关的异常
MemberAccessException:用于处理访问类成员失败时所引发的异常。失败可能的原因是没有足够的访问权限,也可能是要访问的成员根本不存在(类与类之间调用时常用)
FileAccessException:该类派生于
MemberAccessException
,用于处理访问字段成员失败所引发的异常。MethodAccessException:该类派生于
MemberAccessException
,用于处理访问方法成员失败所引发的异常。MissingMemberException:该类派生于
MemberAccessException
,用于处理成员不存在时所引发的异常。
与数组有关的异常
IndexOutOfException:该类派生于
SystemException
,用于处理下标超出了数组长度所引发的异常。ArrayTypeMismatchException:该类派生于
SystemException
,用于处理在数组中存储数据类型不正确的元素所引发的异常。RankException:该类派生于
SystemException
,用于处理维数错误所引发的异常。
与IO有关的异常
IOException:用于处理进行文件输入输出操作时所引发的异常。
DirectionNotFoundException:该类派生于
IOException
,用于处理没有找到指定的目录而引发的异常。FileNotFoundException:该类派生于
IOException
,用于处理没有找到文件而引发的异常。EndOfStreamException:该类派生于
IOException
,用于处理已经到达流的末尾而还要继续读数据而引发的异常。FileLoadException:该类派生于
IOException
,用于处理无法加载文件而引发的异常。PathTooLongException:该类派生于
IOException
,用于处理由于文件名太长而引发的异常。
与算术有关的异常
ArithmeticException:用于处理与算术有关的异常。
DivideByZeroException:该类派生于
ArithmeticException
,表示整数货十进制运算中试图除以零而引发的异常。NotFiniteNumberException:该类派生于
ArithmeticException
,表示浮点数运算中出现无穷打或者非负值时所引发的异常。
异常类的基本属性
// 获取或设置导致错误的应用程序或对象的名称。
public virtual string Source { get; set; }
// 获取或设置指向与此异常关联的帮助文件链接。
public virtual string HelpLink { get; set; }
// 获取调用堆栈上的即时框架字符串表示形式。
public virtual string StackTrace { get; }
// 获取引发当前异常的方法。
public MethodBase TargetSite { get; }
// 获取导致当前异常的 System.Exception 实例。
public Exception InnerException { get; }
// 获取描述当前异常的消息。
public virtual string Message { get; }
// 获取或设置 HRESULT(一个分配给特定异常的编码数字值)。
public int HResult { get; protected set; }
// 获取提供有关异常的其他用户定义信息的键/值对集合。
public virtual IDictionary Data { get; }
注意事项
引发或处理异常需要消耗大量的系统资源和执行时间。异常处理一般是作用于一些不可知的情况,而不是处理可知的事件或流控制。例如:我们在封装一些方法时,需要输入参数,如果参数类型不对可能会引发异常。这时,我们要做的不是去怎么处理异常,而是通过断言去修改代码,使其响应正确的结果。
此外,异常处理尽量与 日志 结合使用,要定期检查异常日志,不要让异常长期存在。