年关将至,对于大部分程序员来说,马上就可以闲下来一段时间了,然而在这个闲暇的时间里,唯有争论哪门语言更好可以消磨时光,估计最近会有很多关于java与.net的博文出现,我表示要作为一个吃瓜群众,静静的看着大佬们发表心情。

以上的废话说的够多了,这里就不再废话了,还是切入正题吧。

在项目开发中,对于系统和代码的稳定性和容错性都是有对应的要求。实际开发项目中的代码与样例代码的区别,更多的是在代码的运行的稳定性、容错性、扩展性的比较。因为对于实现一个功能来说,实现功能的核心代码是一样的,可能只是在写法上优化而已,但是在实现某一个操作上使用的类来说,这一点是绝大多数时候是一样的。这样看来,我们在实际开发的过程中,需要考虑的问题比较多,已经不仅仅局限于某一具体的功能实现,更多的是代码的稳定性和扩展性考虑。

以上是在实际开发中需要面对的问题,笔者在最近的博文中,也在考虑这个异常到底需要怎么去写,以及异常到底需要怎么去理解,在博文中,也有不少的园友对异常的写法和处理提出了自己的意见,在这里我就写一下自己的一些理解,可能写的比较浅显和粗略,但是只当是一个引子,可以引出大佬们来谈谈自己的实际项目经验。希望对大家有一个帮助,也欢迎大家提出自己的想法和意见,分享自己的知识和见解。

一.DotNET异常的概述:

谈到异常,我们就需要知道什么叫做异常,万事万物如果我们想去学习,就应该知道我们要学习的东西是什么,这样在心里也好有一个大概的认知。异常是指成员没有完成它的名称宣称可以完成的行动。在.NET中,构造器、获取和设置属性、添加和删除事件、调用操作符重载和调用转换操作符等等都没有办法返回错误代码,但是在这些构造中又需要报告错误,那就必须提供异常处理机制。

在异常的处理中,我们经常使用到的三个块分别是:try块;catch块;finally块。这三个块可以一起使用,也可以不写catch块使用,异常处理块可以嵌套使用,具体的方法在下面会介绍到。

在异常的处理机制中,一般有三种选择:重新抛出相同的异常,向调用栈高一层的代码通知该异常的发生;抛出一个不同的异常,想调用栈高一层代码提供更丰富的异常信息;让线程从catch块的底部退出。

有关异常的处理方式,有一些指导性的建议。

1.恰当的使用finally块:

finally块可以保证不管线程抛出什么类型的异常都可以被执行,finall块一般用来做清理那些已经成功启动的操作,然后再返回调用者或者finally块之后的代码。

2.异常捕捉需适当:

为什么要适当的捕捉异常呢?如下代码,因为我们不能什么异常都去捕捉,在捕获异常后,我们需要去处理这些异常,如果我们将所有的异常都捕捉后,但是没有预见会发生的异常,我们就没有办法去处理这些异常。

如果应用程序代码抛出一个异常,应用程序的另一端则可能预期要捕捉这个异常,因此不能写成一个”大小通吃“的异常块,应该允许该异常在调用栈中向上移动,让应用程序代码针对性地处理这个异常。

在catch块中,可以使用System.Exception捕捉异常,但是最好在catch块末尾重新抛出异常。至于原因在后面会讲解到。

          try
{
var hkml = GetRegistryKey(rootKey);
var subkey = hkml.CreateSubKey(subKey);
if (subkey != null && keyName != string.Empty)
subkey.SetValue(keyName, keyValue, RegistryValueKind.String);
}
catch (Exception ex)
{
Log4Helper.Error("创建注册表错误" + ex);
throw new Exception(ex.Message,ex);
}

3.从异常中恢复:

我们在捕获异常后,可以针对性的写一些异常恢复的代码,可以让程序继续运行。在捕获异常时,需要捕获具体的异常,充分的掌握在什么情况下会抛出异常,并知道从捕获的异常类型派生出了那些类型。除非在catch块的末尾重新抛出异常,否则不要处理或捕获System.Exception异常。

4.维持状态:

一般情况下,我们完成一个操作或者一个方法时,需要调用几个方法组合完成,在执行的过程中会出现前面几个方法完成,后面的方法发生异常。发生不可恢复的异常时回滚部分完成的操作,因为我们需要恢复信息,所有我们在捕获异常时,需要捕获所有的异常信息。

5.隐藏实现细节来维持契约:

有时可能需要捕捉一个异常并重新抛出一个不同的异常,这样可以维系方法的契约,抛出的心异常类型地应该是一个具体的异常。看如下代码:

FileStream fs = null;
try
{
fs = FileStream(); }
catch (FileNotFoundException e)
{
          //抛出一个不同的异常,将异常信息包含在其中,并将原来的异常设置为内部异常
throw new NameNotFoundException();
}
catch (IOException e)
{ //抛出一个不同的异常,将异常信息包含在其中,并将原来的异常设置为内部异常
             throw new NameNotFoundException(); 
}
finally
{
if (fs != null)
{
fs.close();
}
}

以上的代码只是在说明一种处理方式。应该让抛出的所有异常都沿着方法的调用栈向上传递,而不是把他们”吞噬“了之后抛出一个新的异常。如果一个类型构造器抛出一个异常,而且该异常未在类型构造器方法中捕获,CLR就会在内部捕获该异常,并改为抛出一个新的TypeInitialztionException。

二.DotNET异常的常用处理机制:

在代码发生异常后,我们需要去处理这个异常,如果一个异常没有得到及时的处理,CLR会终止进程。在异常的处理中,我们可以在一个线程捕获异常,在另一个线程中重新抛出异常。异常抛出时,CLR会在调用栈中向上查找与抛出的异常类型匹配的catch块。如果没有任何catch块匹配抛出的异常类型,就发生一个未处理异常。CLR检测到进程中的任何线程有一个位处理异常,都会终止进程。

1.异常处理块:

(1).try块:包含代码通常需要执行一些通用的资源清理操作,或者需要从异常中恢复,或者两者都需要。try块还可以包含也许会抛出异常的代码。一个try块至少有一个关联的catch块或finall块。

(2).catch块:包含的是响应一个异常需要执行的代码。catch关键字后的圆括号中的表达式是捕获类型。捕获类型从System.Exception或者其派生类指定。CLR自上而下搜素一个匹配的catch块,所以应该教具体的异常放在顶部。一旦CLR找到一个具有匹配捕获类型的catch块,就会执行内层所有finally块中的代码,”内层finally“是指抛出异常的tey块开始,到匹配异常的catch块之间的所有finally块。

使用System.Exception捕捉异常后,可以采用在catch块的末尾重新抛出异常,因为如果我们在捕获Exception异常后,没有及时的处理或者终止程序,这一异常可能对程序造成很大的安全隐患,Exception类是所有异常的基类,可以捕获程序中所有的异常,如果出现较大的异常,我们没有及时的处理,造成的问题是巨大的。

(3).finally块:包含的代码是保证会执行的代码。finally块的所有代码执行完毕后,线程退出finally块,执行紧跟在finally块之后的语句。如果不存在finally块,线程将从最后一个catch块之后的语句开始执行。

备注:异常块可以组合和嵌套,对于三个异常块的样例,在这里就不做介绍,异常的嵌套可以防止在处理异常的时候再次出现未处理的异常,以上这些就不再赘述。

2.异常处理实例:

(1).异常处理扩展方法:
        /// <summary>
/// 格式化异常消息
/// </summary>
/// <param name="e">异常对象</param>
/// <param name="isHideStackTrace">是否隐藏异常规模信息</param>
/// <returns>格式化后的异常信息字符串</returns>
public static string FormatMessage(this Exception e, bool isHideStackTrace = false)
{
var sb = new StringBuilder();
var count = 0;
var appString = string.Empty;
while (e != null)
{
if (count > 0)
{
appString += " ";
}
sb.AppendLine(string.Format("{0}异常消息:{1}", appString, e.Message));
sb.AppendLine(string.Format("{0}异常类型:{1}", appString, e.GetType().FullName));
sb.AppendLine(string.Format("{0}异常方法:{1}", appString, (e.TargetSite == null ? null : e.TargetSite.Name)));
sb.AppendLine(string.Format("{0}异常源:{1}", appString, e.Source));
if (!isHideStackTrace && e.StackTrace != null)
{
sb.AppendLine(string.Format("{0}异常堆栈:{1}", appString, e.StackTrace));
}
if (e.InnerException != null)
{
sb.AppendLine(string.Format("{0}内部异常:", appString));
count++;
}
e = e.InnerException;
}
return sb.ToString();
}
(2).验证异常:
       /// <summary>
/// 检查字符串是空的或空的,并抛出一个异常
/// </summary>
/// <param name="val">值测试</param>
/// <param name="paramName">参数检查名称</param>
public static void CheckNullOrEmpty(string val, string paramName)
{
if (string.IsNullOrEmpty(val))
throw new ArgumentNullException(paramName, "Value can't be null or empty");
} /// <summary>
/// 请检查参数不是空的或空的,并抛出异常
/// </summary>
/// <param name="param">检查值</param>
/// <param name="paramName">参数名称</param>
public static void CheckNullParam(string param, string paramName)
{
if (string.IsNullOrEmpty(param))
throw new ArgumentNullException(paramName, paramName + " can't be neither null nor empty");
} /// <summary>
/// 检查参数不是无效,并抛出一个异常
/// </summary>
/// <param name="param">检查值</param>
/// <param name="paramName">参数名称</param>
public static void CheckNullParam(object param, string paramName)
{
if (param == null)
throw new ArgumentNullException(paramName, paramName + " can't be null");
} /// <summary>
/// 请检查参数1不同于参数2
/// </summary>
/// <param name="param1">值1测试</param>
/// <param name="param1Name">name of value 1</param>
/// <param name="param2">value 2 to test</param>
/// <param name="param2Name">name of vlaue 2</param>
public static void CheckDifferentsParams(object param1, string param1Name, object param2, string param2Name)
{
if (param1 == param2) {
throw new ArgumentException(param1Name + " can't be the same as " + param2Name,
param1Name + " and " + param2Name);
}
} /// <summary>
/// 检查一个整数值是正的(0或更大)
/// </summary>
/// <param name="val">整数测试</param>
public static void PositiveValue(int val)
{
if (val < 0)
throw new ArgumentException("The value must be greater than or equal to 0.");
}
(3).Try-Catch扩展操作:
        /// <summary>
/// 对某对象执行指定功能与后续功能,并处理异常情况
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="source">值</param>
/// <param name="action">要对值执行的主功能代码</param>
/// <param name="failureAction">catch中的功能代码</param>
/// <param name="successAction">主功能代码成功后执行的功能代码</param>
/// <returns>主功能代码是否顺利执行</returns>
public static bool TryCatch<T>(this T source, Action<T> action, Action<Exception> failureAction,
Action<T> successAction) where T : class
{
bool result;
try
{
action(source);
successAction(source);
result = true;
}
catch (Exception obj)
{
failureAction(obj);
result = false;
}
return result;
} /// <summary>
/// 对某对象执行指定功能,并处理异常情况
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="source">值</param>
/// <param name="action">要对值执行的主功能代码</param>
/// <param name="failureAction">catch中的功能代码</param>
/// <returns>主功能代码是否顺利执行</returns>
public static bool TryCatch<T>(this T source, Action<T> action, Action<Exception> failureAction) where T : class
{
return source.TryCatch(action,
failureAction,
obj => { });
} /// <summary>
/// 对某对象执行指定功能,并处理异常情况与返回值
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <typeparam name="TResult">返回值类型</typeparam>
/// <param name="source">值</param>
/// <param name="func">要对值执行的主功能代码</param>
/// <param name="failureAction">catch中的功能代码</param>
/// <param name="successAction">主功能代码成功后执行的功能代码</param>
/// <returns>功能代码的返回值,如果出现异常,则返回对象类型的默认值</returns>
public static TResult TryCatch<T, TResult>(this T source, Func<T, TResult> func, Action<Exception> failureAction,
Action<T> successAction)
where T : class
{
TResult result;
try
{
var u = func(source);
successAction(source);
result = u;
}
catch (Exception obj)
{
failureAction(obj);
result = default(TResult);
}
return result;
} /// <summary>
/// 对某对象执行指定功能,并处理异常情况与返回值
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <typeparam name="TResult">返回值类型</typeparam>
/// <param name="source">值</param>
/// <param name="func">要对值执行的主功能代码</param>
/// <param name="failureAction">catch中的功能代码</param>
/// <returns>功能代码的返回值,如果出现异常,则返回对象类型的默认值</returns>
public static TResult TryCatch<T, TResult>(this T source, Func<T, TResult> func, Action<Exception> failureAction)
where T : class
{
return source.TryCatch(func,
failureAction,
obj => { });
}

本文没有具体介绍try,catch,finally的使用,而是给出一些比较通用的方法,主要是一般的开发者对于三个块的使用都有一个认识,就不再做重复的介绍。

三.DotNET的Exception类分析:

CLR允许异常抛出任何类型的实例,这里我们介绍一个System.Exception类:

1.Message属性:指出抛出异常的原因。

[__DynamicallyInvokable]
public virtual string Message
{
[__DynamicallyInvokable]
get
{
if (this._message != null)
{
return this._message;
}
if (this._className == null)
{
this._className = this.GetClassName();
}
return Environment.GetRuntimeResourceString("Exception_WasThrown", new object[] { this._className });
}
}

由以上的代码可以看出,Message只具有get属性,所以message是只读属性。GetClassName()获取异常的类。GetRuntimeResourceString()获取运行时资源字符串。

2.StackTrace属性:包含抛出异常之前调用过的所有方法的名称和签名。

public static string StackTrace
{
[SecuritySafeCritical]
get
{
new EnvironmentPermission(PermissionState.Unrestricted).Demand();
return GetStackTrace(null, true);
}
}

EnvironmentPermission()用于环境限制,PermissionState.Unrestricted设置权限状态,GetStackTrace()获取堆栈跟踪,具体看一下GetStackTrace()的代码。

internal static string GetStackTrace(Exception e, bool needFileInfo)
{
StackTrace trace;
if (e == null)
{
trace = new StackTrace(needFileInfo);
}
else
{
trace = new StackTrace(e, needFileInfo);
}
return trace.ToString(StackTrace.TraceFormat.Normal);
}
public StackTrace(Exception e, bool fNeedFileInfo)
{
if (e == null)
{
throw new ArgumentNullException("e");
}
this.m_iNumOfFrames = 0;
this.m_iMethodsToSkip = 0;
this.CaptureStackTrace(0, fNeedFileInfo, null, e);
}

以上是获取堆栈跟踪方法的具体实现,此方法主要用户调试的时候。

3.GetBaseException()获取基础异常信息方法。

[__DynamicallyInvokable]
public virtual Exception GetBaseException()
{
Exception innerException = this.InnerException;
Exception exception2 = this;
while (innerException != null)
{
exception2 = innerException;
innerException = innerException.InnerException;
}
return exception2;
}

InnerException属性是内在异常,这是一个虚方法,在这里被重写。具体看一下InnerException属性。

[__DynamicallyInvokable]
public Exception InnerException
{
[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
get
{
return this._innerException;
}
}

4.ToString()将异常信息格式化。

private string ToString(bool needFileLineInfo, bool needMessage)
{
string className;
string str = needMessage ? this.Message : null;
if ((str == null) || (str.Length <= 0))
{
className = this.GetClassName();
}
else
{
className = this.GetClassName() + ": " + str;
}
if (this._innerException != null)
{
className = className + " ---> " + this._innerException.ToString(needFileLineInfo, needMessage) + Environment.NewLine + " " + Environment.GetRuntimeResourceString("Exception_EndOfInnerExceptionStack");
}
string stackTrace = this.GetStackTrace(needFileLineInfo);
if (stackTrace != null)
{
className = className + Environment.NewLine + stackTrace;
}
return className;
}

在此方法中,将获取的异常信息进行格式化为字符串,this.GetClassName() 获取异常类的相关信息。

以上我们注意到[__DynamicallyInvokable]定制属性,我们看一下具体的实现代码:

[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public __DynamicallyInvokableAttribute()
{
}

以上我们主要注释部分,”图像边界“这个属性的相关信息,请参见《Via CLR c#》,这里就不做具体的介绍。

四.总结:

以上在对异常的介绍中,主要介绍了CLR的异常处理机制,一些较为通用的异常代码,以及对Exception类的介绍。在实际的项目中,我们一般不要将异常直接抛出给客户,我们在编写程序时,已经考虑程序的容错性,在程序捕获到异常后,尽量去恢复程序,或者将异常信息写入日志,让程序进入错误页。如果出现比较严重的异常,最后将异常抛出,终止程序。

原文链接:http://www.cnblogs.com/pengze0902/p/6185952.html


感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮。本文欢迎各位转载,但是转载文章之后必须在文章页面中给出作者和原文连接。如果需要获取最新的优秀.NET博文,请关注微信公众号“DotNet技术分享”。 

爱知求真,静心钻研,虚心学习,务实创新,细致平和。
 
 

关于.NET异常处理的思考(上)的更多相关文章

  1. 关于.NET异常处理的思考

    年关将至,对于大部分程序员来说,马上就可以闲下来一段时间了,然而在这个闲暇的时间里,唯有争论哪门语言更好可以消磨时光,估计最近会有很多关于java与.net的博文出现,我表示要作为一个吃瓜群众,静静的 ...

  2. SpringMVC入门(二)—— 参数的传递、Controller方法返回值、json数据交互、异常处理、图片上传、拦截器

    一.参数的传递 1.简单的参数传递 /* @RequestParam用法:入参名字与方法名参数名不一致时使用{ * value:传入的参数名,required:是否必填,defaultValue:默认 ...

  3. JAVAEE——SpringMVC第二天:高级参数绑定、@RequestMapping、方法返回值、异常处理、图片上传、Json交互、实现RESTful、拦截器

    1. 课前回顾 https://www.cnblogs.com/xieyupeng/p/9093661.html 2. 课程计划 1.高级参数绑定 a) 数组类型的参数绑定 b) List类型的绑定 ...

  4. JAVA异常处理机制分析(上)

    过去曾有一段时间关于java的异常处理机制曾经让我吃尽苦头,异常机制看似简单,原理,用法也仅仅如此,但是,用起来或是在使用一些框架的时候总会因为使用不当,造成灾难性后果. jdk异常处理机制     ...

  5. hreadPoolExecutor使用和思考(上)-线程池大小设置与BlockingQueue的三种实现区别

    阅读更多 工作中多处接触到了ThreadPoolExecutor.趁着现在还算空,学习总结一下. 前记: jdk官方文档(javadoc)是学习的最好,最权威的参考. 文章分上中下.上篇中主要介绍Th ...

  6. ThreadPoolExecutor使用和思考(上)-线程池大小设置与BlockingQueue的三种实现区别

    工作中多处接触到了ThreadPoolExecutor.趁着现在还算空,学习总结一下. 前记: jdk官方文档(javadoc)是学习的最好,最权威的参考. 文章分上中下.上篇中主要介绍ThreadP ...

  7. springboot2.0全局异常处理,文件上传过大会导致,方法被执行两次,并且连接被重置

    最后发现是内嵌tomcat也有文件大小限制,默认为2MB,我上传的是4MB,然后就炸了.在application.properties中添加server.tomcat.max-swallow-size ...

  8. 关于java异常处理的思考

    学习java的过程中,初学者更多的是为了实验而写代码,而不考虑实际情况中的人机交互过程中的一些问题. 在java项目中,更多的用户不会因为你给了某些限制提醒,他就一定会按照你所给的提示来输入或者操作, ...

  9. SpringMVC 上传下载 异常处理

    SpringMVC 上传下载 异常处理 上一章节对SpringMVC的表单验证进行了详细的介绍,本章节介绍SpringMVC文件的上传和下载(重点),国际化以及异常处理问题.这也是SpringMVC系 ...

随机推荐

  1. 3. SQL Server数据库状态监控 - 可用空间

    原文:3. SQL Server数据库状态监控 - 可用空间 数据库用来存放数据,那么肯定需要存储空间,所以对磁盘空间的监视自然就很有必要了. 一. 磁盘可用空间 1. 操作系统命令或脚本.接口或工具 ...

  2. oracle存储过程+游标处理select数据

    create or replace PROCEDURE UPDATE_RECORDCODE is cursor location_data is select * from location wher ...

  3. POJ 3624 Charm Bracelet 背包问题的解决方案

    最简单的背包问题,标题应该是除了背包测试中心:您无法打开二维数组.我还没有开的二维.光看数据是不可能的. 太大. 有两种方法来提高全省内存DP: 1 所谓卷的阵列 2 反向表 久没做背包DP,突然认为 ...

  4. openwrt补丁

    http://wiki.openwrt.org/doc/devel/patches 中文文档:http://andelf.diandian.com/post/2013-05-22/4005067737 ...

  5. Bootstrap Paginator分页插件

    Bootstrap Paginator分页插件使用示例 最近做的asp.netMVC项目中需要对数据列表进行分类,这个本来就是基于bootstrap开发的后台,因此也就想着bootstrap是否有分页 ...

  6. 使用JSmooth制造java jar文件可以运行exe文件教程图像

    这是我之前在个人博客3yj上面写的一篇文章,如今转载过来,原文地址 (这不是广告哦) 几年前,刚接触java的是.就想用一些方法把自己的劳动果实保护起来,曾经也用过非常多这种工具.有一个特别好用,今天 ...

  7. fullCalendar:中文API

    1.与google日历连接,别忘记加入<script type='text/javascript' src='js/gcal.js'/> events: $.fullCalendar.gc ...

  8. px/em/pt区别和转换

    老是被人问到px.pt和em的区别,自己有时候也会纠结到底该用什么单位,今天特意查了一些文章,下面这篇虽然很久远了,但解释的比较全面,转载收藏之.点击查看原文 这里引用的是Jorux的“95%的中国网 ...

  9. Ubuntu中的.bashrc文件

    /etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.并从/etc/profile.d目录的配置文件中搜集shell的设置./etc/bashrc:为每一个运 ...

  10. Ubuntu下LaTex中文环境安装与配置

    转载自:http://www.linuxidc.com/Linux/2012-06/62456.htm LaTeX是一个强大的排版软件,但是其最初只是为英文排版而设计的.为了使其能够胜任中文排版的重任 ...