【C# 异常处理】 开端
异常概述
在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。
设计类型时要想好各种使用情况类型名称通常是名词,例如filestream或者string builder,然后要为类型定义属性、方法、事件等这些成员的定义方式(属性的数据类型、方法的参数、返回的值等)就是类型的编程接口,这些成员代表类本身或者类型实力能执行的行动,行动成员通常动词表示,例如,read,write,flush、Apend、Insert和remove等。当行动成员不能完成任务时,应抛出异常。在 .NET中,异常是从 System.Exception 类继承的对象。 异常引发自发生问题的代码区域。 异常在堆栈中向上传递,直到应用程序对其进行处理或者程序终止。
定义
异常指成员没有完成它的名称所宣称的行动。
异常分类: 托管异常和CLR内部异常(java 中用Error表示)
当我们提到CLR里的“异常”,要注意一个很重要的区别。有通过如C#的try/catch/finally暴露给应用程序,并由运行时提供机制全权实现的托管异常。也有运行时自己使用的异常。大部分运行时开发人员很少需要想到如何实现并暴露托管异常模型。但每个运行时开发人员都应该懂得CLR实现里是怎么使用异常的。为了保持区分,本文将托管程序抛出并捕捉的称为托管异常,而将运行时自己使用的错误处理方式称为 CLR内部异常。本文主要讨论CLR内部异常。
通常说异常指托管异常,CLR内部异常程序无法处理。
避免使用异常的情况
使用错误(未进行null检测)、程序错误(输入数字错误)、系统失败(栈溢出)这些都是运行错误。但是要用正常方法处理避免使用异常处理的方式。
异常处理机制Message 包含在返回的信息中 ToString 。 Message仅在创建时设置属性 Exception 。 如果没有向当前实例的构造函数提供消息,则系统提供使用当前系统区域性进行格式设置的默认消息。
void FirstTimeException()
{
//出现异常
try {throw new AppException("Base异常"); }
catch (Exception e) { throw new AppException("Second Exception", e); }//不处理继续网上抛
}
class AppException : Exception
{
public AppException(string mess) : base(mess) { }
public AppException(string mess, Exception inner) : base(mess, inner) { }
}
Exception.InnerException 属性
如果当前异常是在处理一个异常的时候抛出的,那么该属性就指向上个异常。 InnerException 属性返回的值与传递到 Exception(String, Exception) 构造函数中的值相同,如果没有向构造函数提供内部异常值,则为 null。 此属性为只读。
如果每次都抛出的异常,经过封装后往上抛。那么就会形成链表结构。就可以通过Exception 公共的方法GetBaseException遍历异常构成的链表,并返回最初的异常。
using System.Collections;
PrinteException();
void PrinteException()
{
try{ThirdTimeException();}
catch (Exception e)
{
//输出所以异常
while (e != null)
{
Console.WriteLine(e.Message);
e = e.InnerException;
}
}}
void ThirdTimeException()
{
try{ FirstTimeException(); }
catch (Exception e) { throw new AppException("tird Exception", e); }//不处理继续网上抛
}
void FirstTimeException()
{
//出现异常
try {throw new AppException("Base异常"); }
catch (Exception e) { throw new AppException("Second Exception", e); }//不处理继续网上抛
}
class AppException : Exception
{
public AppException(string mess) : base(mess) { }
public AppException(string mess, Exception inner) : base(mess, inner) { }
}
Exception 提供了一个公共的方法GetBaseException内层异常异常构成的链表,并返回最初的异常
public virtual Exception GetBaseException()
{
Exception? inner = InnerException;
Exception back = this;
while (inner != null)
{
back = inner;
inner = inner.InnerException;
}
return back;
}
Exception.StackTrace 属性
该属性依赖于.pdb文件配合。如果没有.pdb文件将无法输出源代码中的文件路径和行号、列号。
输出的结果取决抛出的方式:throw或throw e
采用了。方法调用的跟踪称为堆栈跟踪。堆栈跟踪列表提供了一种方法,可以跟踪调用堆栈到发生异常的方法中的行号。
StackTrace属性返回在抛出异常的位置产生的调用堆栈的帧。你可以通过创建System.Diagnostics.StackTrace类的新实例,并使用它的【StackTrace.ToString() 】方法来获取调用堆栈中其他帧的信息。
每当在应用程序代码中抛出异常时(通过使用throw关键字),公共语言运行时(CLR)都会更新堆栈跟踪。如果异常在与最初抛出异常的方法不同的方法中被重新抛出,则堆栈跟踪将包含最初抛出异常的方法中的位置,以及重新抛出异常的方法中的位置。如果在相同的方法中抛出异常,然后重新抛出异常,则堆栈跟踪只包含重新抛出异常的位置,而不包含最初抛出异常的位置。
- 实际是实时访问了CLR中的代码,并且返回一个调用堆栈跟踪的字符串。
异常抛出时,CLR在内部记录throw指令的位置(抛出位置)。一个catch块捕捉到该异常时,CLR记录捕捉的位置。在catch块内访问被抛出的异常对象StackTrace属性,负责实现该属性的代码会调用CLR内部的代码,返回一个字符串来指出从异常抛出位置到异常捕捉位置的所有方法。
抛出异常时,CLR会重置异常起点。也就是说,CLR只记录最新的异常对象的抛出位置。(如果同一个exception,catch之后不做处理,再将其抛出,由于是同一个异常的缘故,所以还是可以获得StackTrace信息的)
- 构造Exception的派生类型时,StackTrace的初值是null。
- StackTrace属性返回的字符串不包含调用栈中比异常发生出更早的方法。即只包含到了异常发生的地方。
- 要获得从线程起始处得到异常处理程序之间的完整堆栈跟踪,需要使用System.Diagnostics.StackTrace类型。
StackTrace属性报告的方法调用可能没有预期的那么多:
1、因为在优化过程中会发生代码转换(比如内联)。
2、调用栈记录的是线程的返回位置(而非来源的位置)
使用案例:
using System.Collections;
PrinteException();
void PrinteException()
{
try{ThirdTimeException();}
catch (Exception e)
{
Console.WriteLine(e.StackTrace);
}}
void ThirdTimeException()
{
try{ FirstTimeException(); }
catch (Exception e) { throw new AppException("tird Exception", e); }//不处理继续网上抛
}
void FirstTimeException()
{
//出现异常
try {throw new AppException("Base异常"); }
catch (Exception e) { throw new AppException("Second Exception", e); }//不处理继续网上抛
}
class AppException : Exception
{
public AppException(string mess) : base(mess) { }
public AppException(string mess, Exception inner) : base(mess, inner) { }
}
/* 输出:
at Program.<$>g__ThirdTimeException|0_1() in C:\Users\HP\source\repos\ConsoleApp10\ConsoleApp10\Program.cs:line 15
at Program.<$>g__PrinteException|0_0() in C:\Users\HP\source\repos\ConsoleApp10\ConsoleApp10\Program.cs:line 6
*/
Exception.Source 属性
包含生成异常的程序集名称
using System.Collections;
PrinteException();
void PrinteException()
{
try{ FirstTimeException(); }
catch (Exception e)
{
Console.WriteLine(e.Source); ///包含生成异常的程序集名称
}}
void FirstTimeException()
{
//出现异常
try {throw new AppException("Base异常"); }
catch (Exception e) { throw new AppException("Second Exception", e); }//不处理继续网上抛
}
class AppException : Exception
{
public AppException(string mess) : base(mess) { }
public AppException(string mess, Exception inner) : base(mess, inner) { }
}
/*
输出:ConsoleApp10*/
Exception.TargetSite属性
找到最初抛出异常的那个方法
using System.Collections;
PrinteException();
void PrinteException()
{
try{ FirstTimeException(); }
catch (Exception e)
{
Console.WriteLine(e.TargetSite); //Void <$>g__FirstTimeException|0_1()
}
}
void FirstTimeException()
{
//出现异常
try {throw new AppException("Base异常"); }
catch (Exception e) { throw new AppException("Second Exception", e); }//不处理继续网上抛
}
class AppException : Exception
{
public AppException(string mess) : base(mess) { }
public AppException(string mess, Exception inner) : base(mess, inner) { }
}
/*
输出:Void <
自定义异常类
现在的实际使用和MSDN文档都建议直接从System.Exception派生自定义的异常。
下面是创建一个自定义异常类型的几个原则:
1,声明序列化,这样可以跨AppDomain访问。
2,添加序列化信息的构造函数,访问级别设为private或protected。
3、Exception()(使用默认值)。
Exception(String),它接受字符串消息。
Exception(String, Exception),它接受字符串消息和内部异常。
4、.NET 可提供由基类 Exception 最终派生的异常类层次结构。 然而,如果预定义的异常都不符合需求,可通过从 Exception 类派生来创建自己的异常类。
5、创建自己的异常时,用户定义的异常类的名称需要以“Exception”一词结尾,并实现三个常见的构造函数,如以下示例所示。 该示例定义名为 EmployeeListNotFoundException 的新异常类。 该类从 Exception 派生,且包含三个构造函数。
using System;
[Serializable]
public class EmployeeListNotFoundException : Exception
{
public EmployeeListNotFoundException()
{
}
public EmployeeListNotFoundException(string message)
: base(message)
{
}
public EmployeeListNotFoundException(string message, Exception inner)
: base(message, inner)
{
}
// 如果异常需要跨应用程序域、跨进程或者跨计算机抛出,就需要能被序列化。
protected InvalidDepartmentException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
microsoft如何创建用户定义的异常
使用远程处理时,必须确保所有用户定义的异常的元数据在服务器(被调用方)可用,在客户端(代理对象或调用方)也可用。 有关详细信息,请参阅异常的最佳做法。
指导原则和最佳实践
善用finally块该不该引发异常 小节中我们说到一个异常会被引发,是因为某个方法声称的任务没有成功完成(失败),而失败的原因有四种:
- 方法的使用者用错了(没有按照方法的契约使用)
- 方法的执行代码写错了
- 方法执行时所在的环境不符合预期
简单说来,就是:使用错误,实现错误、环境错误。
使用错误:
ArgumentException 表示参数使用错了
ArgumentNullException 表示参数不应该传入 null
ArgumentOutOfRangeException 表示参数中的序号超出了范围
InvalidEnumArgumentException 表示参数中的枚举值不正确
InvalidOperationException 表示当前状态下不允许进行此操作(也就是说存在着允许进行此操作的另一种状态)
ObjectDisposedException 表示对象已经 Dispose 过了,不能再使用了
NotSupportedException 表示不支持进行此操作(这是在说不要再试图对这种类型的对象调用此方法了,不支持)
PlatformNotSupportedException 表示在此平台下不支持(如果程序跨平台的话)
NotImplementedException 表示此功能尚在开发中,暂时请勿使用
实现错误:
前面由 CLR 抛出的异常代码主要都是实现错误
NullReferenceException 试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了
IndexOutOfRangeException 使用索引的时候超出了边界
InvalidCastException 表示试图对某个类型进行强转但类型不匹配
StackOverflowException 表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归
OutOfMemoryException 表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了
AccessViolationException 这说明使用非托管内存时发生了错误
BadImageFormatException 这说明了加载的 dll 并不是期望中的托管 dll
TypeLoadException 表示类型初始化的时候发生了错误
环境错误:
IOException 下的各种子类
Win32Exception 下的各种子类
- ……
另外,还剩下一些不应该抛出的异常,例如过于抽象的异常和已经过时的异常,这在前面一小结中有说明。
null。 此属性为只读。- 实际是实时访问了CLR中的代码,并且返回一个调用堆栈跟踪的字符串。
异常抛出时,CLR在内部记录throw指令的位置(抛出位置)。一个catch块捕捉到该异常时,CLR记录捕捉的位置。在catch块内访问被抛出的异常对象StackTrace属性,负责实现该属性的代码会调用CLR内部的代码,返回一个字符串来指出从异常抛出位置到异常捕捉位置的所有方法。
抛出异常时,CLR会重置异常起点。也就是说,CLR只记录最新的异常对象的抛出位置。(如果同一个exception,catch之后不做处理,再将其抛出,由于是同一个异常的缘故,所以还是可以获得StackTrace信息的) - 构造Exception的派生类型时,StackTrace的初值是null。
- StackTrace属性返回的字符串不包含调用栈中比异常发生出更早的方法。即只包含到了异常发生的地方。
- 要获得从线程起始处得到异常处理程序之间的完整堆栈跟踪,需要使用System.Diagnostics.StackTrace类型。
StackTrace属性报告的方法调用可能没有预期的那么多:
下面是创建一个自定义异常类型的几个原则:
1,声明序列化,这样可以跨AppDomain访问。
2,添加序列化信息的构造函数,访问级别设为private或protected。
Exception(String),它接受字符串消息。
Exception(String, Exception),它接受字符串消息和内部异常。
EmployeeListNotFoundException 的新异常类。 该类从 Exception 派生,且包含三个构造函数。- 方法的使用者用错了(没有按照方法的契约使用)
- 方法的执行代码写错了
- 方法执行时所在的环境不符合预期
简单说来,就是:使用错误,实现错误、环境错误。
使用错误:
ArgumentException表示参数使用错了ArgumentNullException表示参数不应该传入 nullArgumentOutOfRangeException表示参数中的序号超出了范围InvalidEnumArgumentException表示参数中的枚举值不正确InvalidOperationException表示当前状态下不允许进行此操作(也就是说存在着允许进行此操作的另一种状态)ObjectDisposedException表示对象已经 Dispose 过了,不能再使用了NotSupportedException表示不支持进行此操作(这是在说不要再试图对这种类型的对象调用此方法了,不支持)PlatformNotSupportedException表示在此平台下不支持(如果程序跨平台的话)NotImplementedException表示此功能尚在开发中,暂时请勿使用
实现错误:
前面由 CLR 抛出的异常代码主要都是实现错误
NullReferenceException试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了IndexOutOfRangeException使用索引的时候超出了边界InvalidCastException表示试图对某个类型进行强转但类型不匹配StackOverflowException表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归OutOfMemoryException表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了AccessViolationException这说明使用非托管内存时发生了错误BadImageFormatException这说明了加载的 dll 并不是期望中的托管 dllTypeLoadException表示类型初始化的时候发生了错误
环境错误:
IOException下的各种子类Win32Exception下的各种子类- ……
另外,还剩下一些不应该抛出的异常,例如过于抽象的异常和已经过时的异常,这在前面一小结中有说明。