解析C#中断言与异常的应用方式及异常处理的流程控制
断言与异常(Assertion Vs Exception) 异常的使用场景:用于捕获外部的可能错误 断言的使用场景:用于捕获内部的不可能错误 我们可以先仔细分析一下我们在.net中已经存在的异常。
首先,我们先不将它们看成异常,因为我们现在还没有在异常和断言之间划清界限,我们先将它们看成错误。 当我们在编码的第一现场考虑到可能会出现文件加载的错误或者服务器错误后,我们的第一直觉是这不是我们代码的问题,这是我们代码之外的问题。 例如下面这段代码 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是不行的,还得加入一个辅助变量。 但是,如果用异常,这个处理就简单多了。可以直接在最内层的抛出异常,在最外层(或是流程控制需要的地方)捕获异常。 在上面的代码中,异常处理起到了流程控制的作用,而不仅仅传递错误信息,对代码的简化做出了贡献。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |