解析C#中断言与异常的应用方式及异常处理的流程控制
断言与异常(Assertion Vs Exception)
在日常编程实践中,断言与异常的界限不是很明显,这也使得它们常常没有被正确的使用。我也在不断的与这个模糊的怪兽搏斗,仅写此文和大家分享一下我的个人看法。我想我们还可以从很多角度来区别断言和异常的使用场景,欢迎大家的意见和建议。
异常的使用场景:用于捕获外部的可能错误
断言的使用场景:用于捕获内部的不可能错误
我们可以先仔细分析一下我们在.net中已经存在的异常。
- System.IO.FileLoadException
- SqlException
- IOException
- ServerException
首先,我们先不将它们看成异常,因为我们现在还没有在异常和断言之间划清界限,我们先将它们看成错误。
当我们在编码的第一现场考虑到可能会出现文件加载的错误或者服务器错误后,我们的第一直觉是这不是我们代码的问题,这是我们代码之外的问题。
例如下面这段代码
public void WriteSnapShot(string fileName, IEnumerable<DbItem> items) { string format = "{0}\t{1}\t{2}\t{3}\t{4}\t{5}"; using (FileStream fs = new FileStream(fileName, FileMode.Create)) { using (StreamWriter sw = new StreamWriter(fs, Encoding.Unicode)) { ... foreach (var item in items) { sw.WriteLine(string.Format(format, new object[]{ item.dealMan, item.version, item.priority, item.bugStatus, item.bugNum, item.description})); } sw.Flush(); } } }
上面的代码在写入文件,很显然会导致IOException。稍微有经验的程序员都会考虑到IO上可能出问题,那我们应该如何处理这个问题呢?在这个上下文中,我们别无它法,只能让这个错误继续往上抛,通知上面一层的调用者,有一个错误发生了,至于上一层调用者会如何处理,不是这个函数要考虑的问题。但在这个函数中,要记得一点,将当前函数中所占用的资源释放了。因此,当我们不能控制的外部错误出现时,我们可以将其作为异常往上抛,这时,我们该使用异常。
现在再来看看断言,我们还是以下面的一段代码为例子。
public Entities.SimpleBugInfo GetSimpleBugInfo(string bugNum) { var selector = DependencyFactory.Resolve<ISelector>(); var list = selector.Return<Entities.SimpleBugInfo>( reader => new Entities.SimpleBugInfo { bugNum = reader["bugNum"].ToString(), dealMan = reader["dealMan"].ToString(), description = reader["description"].ToString(), size = Convert.ToInt32(reader["size"]), fired = Convert.ToInt32(reader["fired"]), }, "select * from bugInfo", new WhereClause(bugNum, "bugNum")); Trace.Assert(list != null); if (list.Count == 0) return null; else return list[0]; }
当我贴出这段代码时,心情有些坎坷,因为我本人在这里也纠结了很久,这也是我一直没有将断言和异常划清界线的原因之一。
首先我们来回顾一下之前定义的断言使用场景:内部不可能发生的错误。
selector.Return这段代码是不是内部代码?如果我们能够修改Return中的代码,说明它是内部代码;反之,说明它是外部代码。对于内部代码,我们可以用断言来保护其逻辑的不变性,当断言被触发时,我们就可以确信是内部代码的错误,我们应该立即修复。
再纠结一下,假设Return是外部代码,我们没有办法去修改它。那么上面的代码可以有两种写法(如果你有更多的想法,请赐教)。
第一种,直接抛出异常。
If(list == null) { throw new NullReferenceException(); }
第二种,调整代码。
if(list == null || list.Count == 0) { return null; } else { return list[0]; }
当然,还有一种就是什么也不做,让代码执行下去直至系统为你抛出空引用错误。但这种做法违背了防卸性编程的原则,我们总是应行尽早或离错误的发生地最近的地方处理错误,避免错误数据流向系统的其它地方,产生更加严重的错误。
总结
对异常或断言的使用取决于你要防卸的是一个内部错误还是外部错误以及你认为它是一个内部错误或外部错误。如果你决定防卸一个内部错误,那请果断使用断言,反之,请使用异常。
异常处理
异常处理对于流程的控制,就像抛出与捕获一样分为两个方面:
如果错误(或某种情况)发生,是否允许程序的控制流继续执行下去(异常的抛出)
如果当前有异常发生,当前的代码是否有机会让程序的控制流进入到一个合理的状态(异常的捕获)
我认为可以用以上两条,作为判断异常处理的准绳。其实大家现在应该可以发现,这个所谓的准绳的着重点在于异常对于流程的影响,而不再是在什么情况下才使用异常。
对于流程控制,最直接的莫过于下面这段代码
try { foreach (var lockGroup in lockGroups) { ... foreach (var newlock in lockGroup.ToArray()) { ... if (diningBlocks.Exists(n => testLockRange.IsOverlapped(n.StartTime, n.EndTime))) { status = LockStatus.InResourceBlock; throw new LockException(); } var diningAvail = availabilities.Find(n => n.Time == newlock.StartTime.TimeOfDay); if (diningAvail == null) { status = LockStatus.Failed; throw new LockException(); } ... if (newLockQuantity > diningAvail.MaxAvail && !canOverrideLock.AllowOverBook) { status = LockStatus.Override; throw new LockException(); } else if (newLockQuantity + reservedQuantity + currentLockedAvail > diningAvail.MaxAvail && !canOverrideLock.AllowOverBook) { status = LockStatus.Override; throw new LockException(); } ... } } } catch (LockException) { return new DiningLock[] { }; }
在上面的代码中,有两层for循环,当最内层出现某种情况时,要求停止整个for循环的执行,显然用两个break是不行的,还得加入一个辅助变量。
但是,如果用异常,这个处理就简单多了。可以直接在最内层的抛出异常,在最外层(或是流程控制需要的地方)捕获异常。
在上面的代码中,异常处理起到了流程控制的作用,而不仅仅传递错误信息,对代码的简化做出了贡献。
上一篇:日常收集C#接口知识(知识全面)
栏 目:C#教程
下一篇:C# 调用 JavaWebservice服务遇到的问题汇总
本文标题:解析C#中断言与异常的应用方式及异常处理的流程控制
本文地址:https://www.xiuzhanwang.com/a1/C_jiaocheng/6762.html
您可能感兴趣的文章
- 01-10C#通过反射获取当前工程中所有窗体并打开的方法
- 01-10C#实现Winform中打开网页页面的方法
- 01-10C#实现由四周向中心缩小的窗体退出特效
- 01-10Extjs4如何处理后台json数据中日期和时间
- 01-10C#中DataGridView常用操作实例小结
- 01-10C#编程获取资源文件中图片的方法
- 01-10asp.net中XML如何做增删改查操作
- 01-10C#利用反射技术实现去掉按钮选中时的边框效果
- 01-10C#中查找Dictionary中的重复值的方法
- 01-10C#中TreeView实现适合两级节点的选中节点方法
阅读排行
本栏相关
- 01-10C#通过反射获取当前工程中所有窗体并
- 01-10关于ASP网页无法打开的解决方案
- 01-10WinForm限制窗体不能移到屏幕外的方法
- 01-10WinForm绘制圆角的方法
- 01-10C#实现txt定位指定行完整实例
- 01-10WinForm实现仿视频播放器左下角滚动新
- 01-10C#停止线程的方法
- 01-10C#实现清空回收站的方法
- 01-10C#通过重写Panel改变边框颜色与宽度的
- 01-10C#实现读取注册表监控当前操作系统已
随机阅读
- 08-05DEDE织梦data目录下的sessions文件夹有什
- 08-05dedecms(织梦)副栏目数量限制代码修改
- 01-10C#中split用法实例总结
- 01-10delphi制作wav文件的方法
- 01-10使用C语言求解扑克牌的顺子及n个骰子
- 01-10SublimeText编译C开发环境设置
- 04-02jquery与jsp,用jquery
- 08-05织梦dedecms什么时候用栏目交叉功能?
- 01-11ajax实现页面的局部加载
- 01-11Mac OSX 打开原生自带读写NTFS功能(图文