• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

关于.NET异常处理的思考(上)

c# 搞代码 4年前 (2022-01-09) 20次浏览 已收录 0个评论

在项目开发中,对于系统和代码的稳定性和容错性都是有对应的要求。实际开发项目中的代码与样例代码的区别,更多的是在代码的运行的稳定性、容错性、扩展性的比较。

因为对于实现一个功能来说,实现功能的核心代码是一样的,可能只是在写法上优化而已,但是在实现某一个操作上使用的类来说,这一点是绝大多数时候是一样的。

这样看来,我们在实际开发的过程中,需要考虑的问题比较多,已经不仅仅局限于某一具体的功能实现,更多的是代码的稳定性和扩展性考虑。

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

一.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&gt<i>本文来源gaodai$ma#com搞$$代**码网</i>;        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的使用,而是给出一些比较通用的方法,主要是一般的开发者对于三个块的使用都有一个认识,就不再做重复的介绍。

以上就是关于.NET异常处理的思考(上)的内容,更多相关内容请关注搞代码(www.gaodaima.com)!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:关于.NET异常处理的思考(上)

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址