在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。
设计类型时要想好各种使用情况类型名称通常是名词,例如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检测)、程序错误(输入数字错误)、系统失败(栈溢出)这些都是运行错误。但是要用正常方法处理避免使用异常处理的方式。
.Net Framework异常处理机制是用Windows提供的结构化异常处理(Structured Exception Handling,SHE)机制构建的。
Try块中包含的代码通常需要执行一些资源清理操作,或可能抛出异常需要从异常中恢复,或者是两者皆有。清理代码应放在finally块中。异常恢复的代码应放在一个或多个catch快中。针对应用程序能从中安全恢复的每一种异常,都应创建一个catch块。一个try块至少要有一个catch块或finally块。单独的try块没有意义。
指导原则:很多程序员不知道在一个try块中的放入多少代码。如果一个try块中执行多个可能抛出同一异常的操作,但这些操作有不同的恢复措施,那么对每一个操作都应放在一个单独的try块中。
1.CLR会自上而下的搜索匹配catch中异常类型 ,所以要将派生程度大的异常类型(则子类需在父类上面),放在靠近try的catch语句中,基类类型放在最后,没有派生关系的无顺序要求。如果弄反了这个顺序编译器将报错,因为这样的catch语句不可达。
2.一旦try中异常匹配到其中一个catch就会执行内层所有的finally块中的代码(指try到匹配到catch之间的所有finally)如果没有找到,内层的finally块是不会执行的,这一点要特别注意。,接着执行匹配catch块内容,最后是该catch块对应的finally块(如果有的话)
3、对于匹配的catch块,我们通常的处理有3种:
●重新抛出相同的异常,向上一层栈通知该异常的发生 throw。这种方式,CLR不会重置异常起点
●抛出一个不同的异常,向上一层栈提供更丰富的异常信息 throw ex。这种方式,CLR会重置异常起点
●让线程从catch块的底部退出。(表示已处理这个异常,不会发生新的异常)
可以通过向AppDomain的FirstChanceException事件登记,只要在这个AppDomain内发生的异常,它就会收到通知,这个通知在CLR开始搜索任何catch块之前发生。CLR22章 “CLR寄宿和AppDmain”
该块涨的的代码是清理代码,这些代码只需对try中发起的操作进行清理。catch和finnally中的代码应该非常短(通常1 2行),而且要非常高的成功率,避免自己又抛出异常。
当然,catch块中的异常恢复代码和finally块中的清理代码也有可能失败并抛出一个异常。虽然这种可能性不大,但如果发生这样的事情,CLR会把这种异常当成是finally块之后抛出的异常一样。但此时,CLR不会记录try块中抛出的第一个异常(如果有的话),第一个异常的堆栈信息将丢失。而新抛出的异常很有可能成为一个未处理的异常,CLR会终止这个进程。这是一件好事,因为损坏的状态会被销毁。相较于应用程序继续运行,造成不可预知的结果以及不可能的安全漏洞,这样的好处多得多。
我们认为finally块非常强悍!不管线程抛出什么样的异常,finally块中的代码都保证会执行。应该用finally块清理那些已成功启动的操作,然后再返回调用者或执行finally块之后的代码。Finally块还经常用于显示释放对象以避免资源泄漏
确保清理代码的执行时如此重要,以至于许多编程语言都提供了一些构造来简化清理代码的编写。例如:只要使用了lock,using和foreach语句,C#编译器就会自动生成try/finally块。另外,重写类的析构器(Finalize)时,C#编译器也会自动生成try/catch块。使用这些构造时,编译器将你写的代码放到try块内,并自动将清理代码放在finally块内,具体如下:
●使用lock语句,锁会在finally块中释放。
●使用using语句,会在finally块中调用对象的Dispose方法。
●使用foreach语句,会在finally块中调用IEnumerator对象的Dispose方法。
●定义析构方法时,会在finally块调用基类的Finalize方法。
使用 finally
块清除未实现 IDisposable 的资源。 即使引发了异常,通常也会执行 finally
子句中的代码。
设置vs>“调试”>“Windows”>“异常设置”
搜索OperationCanceledException异常,然后将该异常打勾
调试器中断时,会显示引发异常的位置。
添加或删除自定义异常
更多查看https://www.cnblogs.com/cdaniu/p/15915242.html
属性名称
访问
类型
说明
Message
readonly
String
包含辅助性文字说明,指出抛出异常的原因。如果抛出的异常未处理,该消息通常被写入日志。
Data
readonly
IDictionary
引用一个键值对集合。通常,代码在抛出异常前在该集合中添加记录项;捕捉异常的代码可在异常恢复过程中查询记录项并利用其中的信息
Source
read/write
String
包含生成异常的程序集的名称
StackTrace
readonly
String
包含抛出异常之前调用过的所有方法的名称和签名。即:**包含出错位置的调用堆栈信息,受到抛出异常方式的影响: throw(不会重置)、throw e(重置堆栈的启动)
**
TargetSite
readonly
MethodBase
包含抛出异常的方法
HelpLink
readonly
String
包含异常的文档的URL。将信息传给其他程序员,否则不使用该属性
InnerException
readonly
Exception
如果当前异常是在处理一个异常时抛出的,该属性就指出上一个异常时什么。通常为null。Exception类型提供了GetBaseException来遍历内层异常构成的链表,并返回最初抛出的异常.(至少会得到最初抛出的异常)
HResult
read/write
Int32
跨越托管和本机代码边界时使用的一个32位值。
using System.Collections;
PrinteException();
Console.Read();
void PrinteException()
{
try{ ThirdTimeException(); }
catch (Exception e)
{
foreach (DictionaryEntry de in e.Data)
Console.WriteLine(" Key: {0,-20} Value: {1}", "'" + de.Key.ToString() + "'", de.Value);
Console.WriteLine(e.StackTrace);//StackTrace 受到throw 或throw e 抛出方式的影响
}
}
void ThirdTimeException()
{
//出现异常
try { FirstTimeException(); }
catch (Exception e) {
e.Data.Add("ThirdKyeItme1", "ThirdValueItme1");//直接添加
e.Data["ThirdKyeItme2"] = "ThirdValueItme2"; //索引的方式添加
throw ;//不处理继续网上抛 Clr不会重置堆栈的起点的
// throw e;Clr 会重置堆栈的起点的 会影响e.StackTrace的输出结果
}
}
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 <
获取描述当前异常的消息。
包含辅助性信息,说明异常抛出的原因,该属性主要给辅助开放人员调试,所以尽可能提供详细信息。
属性的值 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) { }
}
如果当前异常是在处理一个异常的时候抛出的,那么该属性就指向上个异常。 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;
}
该属性依赖于.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.<
at Program.<
*/
包含生成异常的程序集名称
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*/
找到最初抛出异常的那个方法
using System.Collections;
PrinteException();
void PrinteException()
{
try{ FirstTimeException(); }
catch (Exception e)
{
Console.WriteLine(e.TargetSite); //Void <
}
}
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 <<Main
现在的实际使用和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) { }
}
使用远程处理时,必须确保所有用户定义的异常的元数据在服务器(被调用方)可用,在客户端(代理对象或调用方)也可用。 有关详细信息,请参阅异常的最佳做法。
本部分中所述的异常引发准则要求对执行失败的含义有一个很好的定义。 每当一个成员无法执行它旨在要执行的操作(成员名称所指的操作)时,执行失败就会发生。 例如,如果 OpenFile
方法不能将打开的文件句柄返回给调用方,这将被视为执行失败。
大多数开发人员已经习惯将异常用于使用错误(例如被零除或空引用)。 在框架中,异常用于所有错误情况,包括执行错误。
请勿返回错误代码。
异常是在框架中报告错误的主要方法。
️ 请务必通过引发异常来报告执行失败。
️ 在代码遇到无法安全地进行进一步执行的情况时,请考虑通过调用 System.Environment.FailFast
(.NET Framework 2.0 功能)来终止进程,而不是引发一个异常。
如果可能,请勿对正常控制流使用异常。
除了可能出现争用条件的系统故障和操作之外,框架设计者还应设计 API,使用户能够编写不引发异常的代码。 例如,你可提供一种在调用成员之前检查前置条件的方法,使用户可以编写不引发异常的代码。
用于检查另一个成员的前置条件的成员通常称为测试者,而实际执行该工作的成员称为执行者。
在某些情况下,“测试者-执行者”模式可能会产生不可接受的性能开销。 在此类情况下,应考虑使用所谓的“尝试-分析”模式(有关详细信息,请参阅异常和性能)。
️ 请考虑引发异常对性能的影响。 每秒 100 次以上的引发率可能会显著影响大多数应用程序的性能。
️ 请务必记录所有由可公开调用的成员因违反成员协定(而不是系统故障)而引发的异常,并将它们视为你协定的一部分。
作为协定一部分的异常不应从一个版本更改为下一个版本(即不应更改异常类型,也不应添加新异常)。
请勿包含可基于某些选项引发或不引发的公共成员。
请勿包含将异常作为返回值或 out
参数返回的公共成员。
从公共 API 返回异常,而不是引发异常,这会使基于异常的错误报告的许多好处无法实现。
️ 请考虑使用异常生成器方法。
从不同的位置引发相同的异常是很常见的情况。 若要避免代码膨胀,请使用创建异常并初始化其属性的帮助程序方法。
此外,引发异常的成员不会被内联。 将 throw 语句移动到生成器中可能会允许该成员内联。
请勿从异常筛选器块中引发异常。
当异常筛选器引发一个异常时,CLR 将捕获该异常,并且筛选器将返回 false。 此行为与筛选器显式执行并返回 false 是无法区分的,因此很难进行调试。
请避免从 finally 块显式引发异常。 由于调用引发的方法而隐式引发的异常是可以接受的。
Portions 2005, 2009 Microsoft Corporation 版权所有。保留所有权利。
在 Pearson Education, Inc. 授权下,由 Addison-Wesley Professional 作为 Microsoft Windows 开发系列的一部分再版自 Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition(Framework 设计准则:可重用 .NET 库的约定、惯例和模式第 2 版),由 Krzysztof Cwalina 和 Brad Abrams 发布于 2008 年 10 月 22 日。
避免异常代理的性能损失的两种方式:“测试者-执行者”模式和“尝试-分析”模式。当一个成员引发一个异常时,其性能可能会慢几个数量级。 但是,在严格遵守不允许使用错误代码的异常准则的同时,也有可能获得良好的性能。
有时,异常引发成员的性能可以通过将该成员分成两部分来提高。 让我们看看 ICollection
ICollection
numbers.Add(1);
如果集合是只读的,则 Add
方法引发。 在方法调用预计会经常失败的情况下,这可能是一个性能问题。 缓解此问题的一种方法是在尝试添加值之前测试集合是否可写。
ICollection
…
if (!numbers.IsReadOnly)
{
numbers.Add(1);
}
用于测试条件的成员(在本示例中为 IsReadOnly
属性)被称为测试者。 用于执行潜在引发操作的成员(在本示例中为 Add
方法)被称为执行者。
️ 请考虑对可能在常见场景中引发异常的成员使用“测试者-执行者”模式,以避免与异常相关的性能问题。
对于对性能极其敏感的 API,应使用比前一部分中所述的“测试者-执行者”模式更快的模式。 该模式要求调整成员名称,使定义完善的测试用例成为成员语义的一部分。 例如,DateTime 定义一个 Parse 方法,当字符串的分析失败时,该方法将引发异常。 它还定义一个相应的 TryParse 方法,该方法尝试进行分析,但如果分析不成功,则返回 false,而如果分析成功,则使用 out
参数返回分析结果。
public struct DateTime
{
public static DateTime Parse(string dateTime)
{
…
}
public static bool TryParse(string dateTime, out DateTime result)
{
…
}
}
使用此模式时,务必严格地定义“尝试”功能。 如果成员由于定义完善的“尝试”功能以外的任何原因失败,该成员仍必须引发相应的异常。
️ 请考虑对可能在常见场景中引发异常的成员使用“尝试-分析”模式,以避免与异常相关的性能问题。
️ 对于实现此模式的方法,请务必使用前缀“Try”和布尔返回类型。
️ 请务必使用“尝试-分析”模式为每个成员提供一个异常引发成员。
我们认为finally块非常强悍!不管线程抛出什么样的异常,finally块中的代码都保证会执行。应该用finally块清理那些已成功启动的操作,然后再返回调用者或执行finally块之后的代码。Finally块还经常用于显示释放对象以避免资源泄漏。如下例:
public static void SomeMethod()
{
FileStream fs = new FileStream(@"c:\test.txt", FileMode.Open);
try
{
//显示用100除以文件第一个字节的结果
Console.WriteLine(100 / fs.ReadByte());
}
finally
{
//清理资源,即使发生异常,文件都能关闭
fs.Close();
}
}
确保清理代码的执行时如此重要,以至于许多编程语言都提供了一些构造来简化清理代码的编写。例如:只要使用了lock,using和foreach语句,C#编译器就会自动生成try/finally块。另外,重写类的析构器(Finalize)时,C#编译器也会自动生成try/catch块。使用这些构造时,编译器将你写的代码放到try块内,并自动将清理代码放在finally块内,具体如下:
●使用lock语句,锁会在finally块中释放。
●使用using语句,会在finally块中调用对象的Dispose方法。
●使用foreach语句,会在finally块中调用IEnumerator对象的Dispose方法。
●定义析构方法时,会在finally块调用基类的Finalize方法。
例如,用using语句代替上面的代码,代码量更少,但编译后的结果是一样的。
使用异常时,新手一个常规的错误是过于频繁或者不恰当的使用catch块。捕捉一个异常表明你预见到该异常,理解它为什么发生,并知道如何处理它。换句话说,是在为应用程序定义一个策略。
但是我们经常会看到如下的代码。
try {
//尝试执行程序员知道可能出错的代码
}
catch(Exception ex) {
…}
这段代码指出它预见了所有的异常类型,并知道如何从所有的状况中恢复。这不是在吹牛吗?
如果一个类型是类库的一部分,那么在任何情况下,它都决不能捕捉并“吞噬”所有异常,因为它不可能预知应用程序具体如何响应一个异常,如果捕获Exception 要向上抛出来。除此之外,通过委托,虚方法和接口方法,类型会经常调用应用程序代码。
如果应用程序抛出一个异常,应用程序的另一部分则可能预期要捕捉这个异常。所以,你绝不能写一个“大小通吃”的类型,悄悄的“吞噬”这个异常,而是应该允许异常在调用栈中向上移动,让应用程序代码有针对性的处理这个异常。
如果异常未得到处理,CLR会终止进程。大多数未处理异常都能在代码测试期间发现。为了修正这些未处理的异常,一个办法是修改代码来捕捉一个特定的异常,要么重写代码排除会造成抛出异常的错误条件。在生产环境中运行的最终版本应该极少出现未处理异常,而且应该相当健壮。
顺便说一句,在一个catch快中,确实可以捕捉System.Exception异常并执行一些代码,只要在catch块的末尾重新抛出异常。千万不要捕捉System.Exception异常并悄悄“吞噬”它,否则应用程序不知道已经出错,还是会继续执行,造成不可预测的结果和潜在的风险。
某些时候,异常的发生造成了对象状态的破坏并且无法恢复。在继续执行程序会造成不可预测的结果和潜在的风险,这是应该调用Environment的FailFast方法来终止进程。
在运行到一些诡异的代码,这时的程序已经无法继续运行,需要退出,那么如何在记完日志之后在退出程序记录更多信息?可以通过 Environment.FailFast 里面添加字符串告诉用户当前的进程无法继续运行。
failfast方法将信息字符串和可选的异常(通常是catch中捕获的异常)写入window application事件日志,生成windows错误报告,创建应用程序的内部转储(dump),然后终止当前进程。
如判断因为“app异常写入window日志”无法继续运行
string str = "app损坏退出";
try
{
if (str == "app损坏退出")
{
Environment.FailFast(str);
}
}
finally
{
Console.WriteLine("其实这个代码不会运行");
}
注意:只能通过双击软件运行。vs2022调试状态运行时,会抛出异常,导致未写入windows日志。
这时程序退出,可以在 Windows 日志里面的应用程序找到这个信息
同时这个方法调用的时候 finally 里面的代码是无法执行的
运行上面的代码是不会看到 finally 的输出.
此方法终止进程,而不运行任何活动 try / finally 的块或终结器。
Environment.FailFast方法将字符串写入 message Windows 应用程序事件日志,创建应用程序的转储,然后终止当前进程。 此 message 字符串也包含在向 Microsoft 报告错误。
使用 Environment.FailFast 方法而不是 Environment.Exit(相当于main中return) 方法来终止应用程序,前提是应用程序的状态已损坏,无法修复,执行应用程序的 try / finally 块和终结器将损坏程序资源。
使用 Windows 错误报告向 Microsoft 报告信息。 有关详细信息,请参阅 Windows 错误报告:入门。
调用 Environment.FailFast 方法以终止在 Visual Studio 调试器中运行的应用程序的执行会引发 ExecutionEngineException ,并自动触发 fatalExecutionEngineError 托管调试助手 (MDA) 。
有时候,在调用一个方法时,已经预料到他可能抛出某些异常。由于预料到这些异常,所以可以写一些代码,允许应用程序从异常中特体的恢复并继续执行。下面是一个伪代码:
public string CalculateSpreadsheetCell(int row, int column)
{
string result = "";
try {
result =//计算电子表格单元格的值
}
catch (DivideByZeroException){//捕捉0除错误
result = "Can't show value: Divide by zero";
}
catch (OverflowException){//捕捉溢出错误
result = "Can't show value: Too Big";
}
return result;
}
上述的伪代码计算电子表格单元格的内容,将代表值的字符串返回给调用者。本例预测了DivideByZeroException和OverflowException两个异常。除非在catch块末尾重写抛出捕捉到的异常,否则不要捕捉System.Exception异常,因为不可能搞清楚try块中全部抛出的异常。例如:try块还可能抛出OutOfMemoryException和StackOverflowException,而这只是所有可能的异常中最普通的两个。
通常,方法要调用其他几个方法来执行一个抽象操作,这些方法可能成功,也可能失败。例如,假定要对一组对象序列化到一个磁盘文件。序列化好第10个对象后,抛出一个异常(可能是磁盘已满,或是序列化的对象没有应用Serializable特性)。在这种情况下,应该将这个异常“漏”给调用者处理,但磁盘文件的状态怎么办呢?文件包含了一个部分序列化的对象图,所以它已经损坏。理想情况下,应用程序应该回滚部分完成的操作,将文件恢复到序列化之前的状态。
public void SerializeObjectGraph(FileStream fs, IFormatter formatter, object rootObj)
{
//保存文件的当前位置
Int64 beforeSerialization = fs.Position;
try {//尝试将对象图序列化到文件
formatter.Serialize(fs, rootObj);
}
catch {//捕捉所有异常
fs.Position = beforeSerialization; //任何出错,就将文件恢复到一个有效的状态
fs.SetLength(fs.Position); //截断文件
throw; //重写抛出相同的异常,让调用者知道发生了什么
}
}
受约束的执行区域- 这是一种通过防止异常中断来使一段代码或多或少具有原子性的方法。受约束的执行区域 (CER) 是创作可靠托管代码的机制的一部分。CER 定义一个区域,在该区域中公共语言运行库 (CLR) 会受到约束,不能引发可使区域中的代码无法完全执行的带外异常。在该区域中,用户代码受到约束,不能执行会导致引发带外异常的代码。PrepareConstrainedRegions 方法必须直接位于 try块之前,并将 catch、finally 和 fault 块标记为受约束的执行区域。标记为受约束的区域后,代码只能调用其他具有强可靠性约定的代码,而且代码不应分配或者对未准备好的或不可靠的方法进行虚调用,除非代码已经准备好处理错误。CLR 为 CER 中正在执行的代码延迟线程中止。
在 该不该引发异常 小节中我们说到一个异常会被引发,是因为某个方法声称的任务没有成功完成(失败),而失败的原因有四种:
简单说来,就是:使用错误,实现错误、环境错误。
使用错误:
ArgumentException
表示参数使用错了ArgumentNullException
表示参数不应该传入 nullArgumentOutOfRangeException
表示参数中的序号超出了范围InvalidEnumArgumentException
表示参数中的枚举值不正确InvalidOperationException
表示当前状态下不允许进行此操作(也就是说存在着允许进行此操作的另一种状态)ObjectDisposedException
表示对象已经 Dispose 过了,不能再使用了NotSupportedException
表示不支持进行此操作(这是在说不要再试图对这种类型的对象调用此方法了,不支持)PlatformNotSupportedException
表示在此平台下不支持(如果程序跨平台的话)NotImplementedException
表示此功能尚在开发中,暂时请勿使用实现错误:
前面由 CLR 抛出的异常代码主要都是实现错误
NullReferenceException
试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了IndexOutOfRangeException
使用索引的时候超出了边界InvalidCastException
表示试图对某个类型进行强转但类型不匹配StackOverflowException
表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归OutOfMemoryException
表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了AccessViolationException
这说明使用非托管内存时发生了错误BadImageFormatException
这说明了加载的 dll 并不是期望中的托管 dllTypeLoadException
表示类型初始化的时候发生了错误环境错误:
IOException
下的各种子类Win32Exception
下的各种子类另外,还剩下一些不应该抛出的异常,例如过于抽象的异常和已经过时的异常,这在前面一小结中有说明。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章