热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

解析C#中断言与异常的应用方式及异常处理的流程控制

这篇文章主要介绍了C#中断言与异常的应用方式及异常处理的流程控制,一般来说断言用于修正程序员自己的错误而异常用于应对程序运行过程中可能出现的错误,需要的朋友可以参考下

断言与异常(Assertion Vs Exception)
在日常编程实践中,断言与异常的界限不是很明显,这也使得它们常常没有被正确的使用。我也在不断的与这个模糊的怪兽搏斗,仅写此文和大家分享一下我的个人看法。我想我们还可以从很多角度来区别断言和异常的使用场景,欢迎大家的意见和建议。

异常的使用场景:用于捕获外部的可能错误

断言的使用场景:用于捕获内部的不可能错误

我们可以先仔细分析一下我们在.net中已经存在的异常。

  • System.IO.FileLoadException
  • SqlException
  • IOException
  • ServerException

首先,我们先不将它们看成异常,因为我们现在还没有在异常和断言之间划清界限,我们先将它们看成错误。

当我们在编码的第一现场考虑到可能会出现文件加载的错误或者服务器错误后,我们的第一直觉是这不是我们代码的问题,这是我们代码之外的问题。

例如下面这段代码

public void WriteSnapShot(string fileName, IEnumerable 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();

      var list = selector.Return(
        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是不行的,还得加入一个辅助变量。

但是,如果用异常,这个处理就简单多了。可以直接在最内层的抛出异常,在最外层(或是流程控制需要的地方)捕获异常。

在上面的代码中,异常处理起到了流程控制的作用,而不仅仅传递错误信息,对代码的简化做出了贡献。


推荐阅读
  • MySQL 数据库迁移指南:从本地到远程及磁盘间迁移
    本文详细介绍了如何在不同场景下进行 MySQL 数据库的迁移,包括从一个硬盘迁移到另一个硬盘、从一台计算机迁移到另一台计算机,以及解决迁移过程中可能遇到的问题。 ... [详细]
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 利用存储过程构建年度日历表的详细指南
    本文将介绍如何使用SQL存储过程创建一个完整的年度日历表。通过实例演示,帮助读者掌握存储过程的应用技巧,并提供详细的代码解析和执行步骤。 ... [详细]
  • MySQL缓存机制深度解析
    本文详细探讨了MySQL的缓存机制,包括主从复制、读写分离以及缓存同步策略等内容。通过理解这些概念和技术,读者可以更好地优化数据库性能。 ... [详细]
  • SQLite 动态创建多个表的需求在网络上有不少讨论,但很少有详细的解决方案。本文将介绍如何在 Qt 环境中使用 QString 类轻松实现 SQLite 表的动态创建,并提供详细的步骤和示例代码。 ... [详细]
  • 本文详细介绍了 MySQL 中 LAST_INSERT_ID() 函数的使用方法及其工作原理,包括如何获取最后一个插入记录的自增 ID、多行插入时的行为以及在不同客户端环境下的表现。 ... [详细]
  • 随着网络安全威胁的不断演变,电子邮件系统成为攻击者频繁利用的目标。本文详细探讨了电子邮件系统中的常见漏洞及其潜在风险,并提供了专业的防护建议。 ... [详细]
  • SQL中UPDATE SET FROM语句的使用方法及应用场景
    本文详细介绍了SQL中UPDATE SET FROM语句的使用方法,通过具体示例展示了如何利用该语句高效地更新多表关联数据。适合数据库管理员和开发人员参考。 ... [详细]
  • 自学编程与计算机专业背景者的差异分析
    本文探讨了自学编程者和计算机专业毕业生在技能、知识结构及职业发展上的不同之处,结合实际案例分析两者的优势与劣势。 ... [详细]
  • 使用Vultr云服务器和Namesilo域名搭建个人网站
    本文详细介绍了如何通过Vultr云服务器和Namesilo域名搭建一个功能齐全的个人网站,包括购买、配置服务器以及绑定域名的具体步骤。文章还提供了详细的命令行操作指南,帮助读者顺利完成建站过程。 ... [详细]
  • 在现代网络环境中,两台计算机之间的文件传输需求日益增长。传统的FTP和SSH方式虽然有效,但其配置复杂、步骤繁琐,难以满足快速且安全的传输需求。本文将介绍一种基于Go语言开发的新一代文件传输工具——Croc,它不仅简化了操作流程,还提供了强大的加密和跨平台支持。 ... [详细]
  • 阅读本文大约需要3分钟。微信8.0版本的发布带来了许多令人振奋的新功能,如烟花特效和改进的悬浮窗,引发了用户的热烈反响。 ... [详细]
  • 深入理解Java泛型:JDK 5的新特性
    本文详细介绍了Java泛型的概念及其在JDK 5中的应用,通过具体代码示例解释了泛型的引入、作用和优势。同时,探讨了泛型类、泛型方法和泛型接口的实现,并深入讲解了通配符的使用。 ... [详细]
author-avatar
一大书虫
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有