zoukankan      html  css  js  c++  java
  • 重学c#系列——异常续[异常注意事项](七)

    前言

    对上节异常的补充,也可以说是异常使用的注意事项。

    正文

    减少try catch的使用

    前面提及到,如果一个方法没有实现该方法的效果,那么就应该抛出异常。

    如果有约定那么可以按照约定,如果约定有歧义,那么还是要抛出异常。

    我们知道使用try catch 其实是要消耗性能的,那么是否能避免使用try catch呢?或者减少使用try catch呢?

    我们使用api的时候,知道http制定了一套错误码,那么我们是否能使用错误码返回来做一个约定的呢?

    答案是否定的,且不论我们的业务的不同,错误码制定的困难,单是方法里面使用错误码偶和性就非常大。

    既然无法通过约定一套错误码来解决,那么是否可以先验证该方法能否执行成功呢?

    public  class ArrayWorker
    {
    	int[] arrays = { 10, 9, 8, 7, 6 };
    	public bool tryWorker(int index)
    	{
    		if (arrays.Length >index&&index>-1)
    		{
    			return true;
    		}
    		return false;
    	}
    	public int DoWorker(int index)
    	{
    		return arrays[index];
    	}
    }
    

    调用:

    static void Main(string[] args)
    {
    	ArrayWorker arrayWorker = new ArrayWorker();
    	if (arrayWorker.tryWorker(-1))
    	{
    		arrayWorker.DoWorker(-1);
    	}
    	else
    	{
    		//错误处理
    	}
    }
    

    调用前可以先检测是否可以执行成功,如果执行成功再去执行,如果不行进行另外的处理。

    当然这种存在局限性,比如说检查条件复杂等。

    异常块using 和 try finally

    我们知道如果非托管资源,且实现了正常的IDisposable,那么我们有两个时候可以去释放资源,一个是我们主动调用dispose,还有一个我们在finalizer中释放。

    那么哪个好呢?当然是dispose中了,原因很简单,如果在finalizer中释放,意味着我们的非托管资源在内存中的事件下降,还有一个我在托管中提及到的所以终结器是单线程,我们是不希望放在这里面去非托管资源的,因为堵塞。

    我通常使用using来避免自己忘记释放资源,有时候也使用try finally,这样写是否代码风格不统一呢?还是说他们之间有区别呢?

    public static void excecuteCommond(string connectString, string commonString)
    {
    	SqlConnection sqlConnection = null;
    	try
    	{
    		 sqlConnection = new SqlConnection(connectString);
    		SqlCommand sqlCommand = null;
    		try
    		{
    			sqlCommand = new SqlCommand(commonString, sqlConnection);
    		}
    		finally
    		{
    			sqlCommand?.Dispose();
    		}
    	}
    	finally
    	{
    		sqlConnection?.Dispose();
    	}
    }
    
    public static void excecuteCommond1(string connectString, string commonString)
    {
    
    	using (SqlConnection sqlConnection = new SqlConnection(connectString))
    	{
    		using (SqlCommand sqlCommand = new SqlCommand(commonString,sqlConnection))
    		{
    
    		}
    	}
    }
    

    上面两个方法在IL上是相等的。实际上using是try finally的语法糖。但是呢,我们写嵌套using,实际上是写嵌套try finally。

    有些人不喜欢嵌套try finally,所以会这样写:

    public static void excecuteCommond(string connectString, string commonString)
    {
    	SqlConnection sqlConnection = null;
    	SqlCommand sqlCommand = null;
    	try
    	{
    		 sqlConnection = new SqlConnection(connectString);
    		 sqlCommand = new SqlCommand(commonString, sqlConnection); 
    	}
    	finally
    	{
    		sqlCommand?.Dispose();
    		sqlConnection?.Dispose();
    		
    	}
    }
    

    然后在非托管异常处理中,发现sqlConnection不仅提供Dispose方法还是存在close。

    这个close 和dispose什么区别呢?在该系列非托管中,Dispose不仅释放自己的资源,还干了一件事就是GC.SuppressFinalize(),也就是说去抑制终结器。

    那么close没有去抑制终结器,所以区别还是挺大的。

    而且在设计上,close 之后,还是可以open connect的,而dispose后不能open connect。close 是去释放现在占有的资源(相当于没收财产,加个死缓),dispose是标记这个对象进入死亡阶段(死刑,一般来说救不回来了)。

    异常保证

    常说,通过try catch来捕获异常,万一真的出现没有捕获的异常怎么破?是不是应该做点啥?

    处理异常实际上,需要三种保障。

    1.基本保障。

    2.强保证

    3.no-throw 保障

    分别介绍这三种不同的意思:

    1.基本保证的要求是:确保异常离开产生该异常的函数后,程序中的资源不会泄露,而且所以对象处于有效状态。

    2.强保证是在基本保证的前提下,他要求整个程序的状态不能因为某操作产生异常而变化。

    3.no-throw 是程序不会发生异常。

    那么这三种类型中,强保证是首推的。

    单纯基本保证感觉又不够,no-throw 太严格,强保证是一种折中方案。

    强保证规定:如果操作出现异常,那么应用程序状态必须和执行前操作是一样的,这样说比较诡异,简单点说是这样子的。

    执行方法的操作要么是完全成功,要不是完全失败。如果失败,那么程序的状态就和执行操作之前一模一样,而不会出现部分成功的情况。

    这不难想象到,如果我们调用一个方法出现了异常,如果方法中某一步出现异常,而数据发送了改变,但是剩余操作无法执行,这时候我们的程序数据将不可控。

    那么这个时候我们如何去实现这个强类型保证呢?通过防御性的拷贝来实现,具体步骤如下:

    1.先把要修改的数据拷贝一份。

    2.在拷贝的数据上面修改,修改过程中,执行那些可能抛出异常的操作。

    3.完全操作成功用拷贝文件去覆盖原数据。

    这么一看,这不是cpu和内存损耗吗?

    是的,但是这增加了程序的健壮性吗?随着时代的发展,分布式集群的发展,性能似乎可以用硬件来堆,程序的稳健性应该放在第一位。

    我们是不是保证了强类型异常,那么是不是no-throw 永远用不到呢?其实提出3个标准的时候,是按照程序的严格顺序来的。强类型异常在异常下,无法继续执行了,但是数据不能部分修改。

    那么no-throw用在什么地方呢?

    下面几个地方一定不能出现异常。

    1.finalizer 与 dispose中不能出现异常。

    2.异常筛选器中不能出现异常。

    3.委托目标中不能出现异常。

    第一个在finalizer 中出现异常,那么线程会中断,可以想象一下终结器中断是啥子问题。

    dispose中释放非托管中出现问题,资源问题即大问题,因为不好恢复。

    异常筛选器中一旦出现异常,那么原先抛出的异常将会被新的异常覆盖,捕获不到原先的异常,就会出现艰难的错误处理问题。

    委托目标这个很好理解了,多播委托中,一旦出现问题,那么后续的方法不会执行。

    这个时候在任何可能出现异常的地方使用try catch,反复检查,捕获异常,从而不会中断程序。

    这一章是对上一节的补充,还有很多细节是无法写完的,就到此吧,后续是一个自定义异常的例子。

    以上只是个人整理,如有问题,请指出。最后卑微的说一句:跪求大佬指点 。

  • 相关阅读:
    uboot的mtd功能支持
    ARM920T系统总线时序分析
    NorFlash
    编译u-boot命令和u-boot常用命令
    uboot启动linux的过程
    debug(fmt,args...)调试
    APCS
    You've got to find what you love
    debian , ubuntu 截取下拉菜单
    关于bash的shellshock漏洞
  • 原文地址:https://www.cnblogs.com/aoximin/p/13426143.html
Copyright © 2011-2022 走看看