zoukankan      html  css  js  c++  java
  • Try 和异常

    Try 以及异常在c#中是很重要的内容,很多开发人员其实并不是很了解try 和异常。在这篇文章中我将会各大家具体讲解一下Try和异常。

    零、try…catch…finally

    1. try
      try 语句是用来进行错误处理或者清理错误的代码块

    2. catch
      catch 代码块可以直接访问 Exception 对象,这个对象中包含了相关的错误信息,catch块通常用来处理错误,或者重新排除异常。

    3. finally
      finally 代码块增加了程序的确定性,CLR会尽力去执行它。finally 通常会被用来做清理任务。

    注意:finally 代码块并不是一定执行的,在某些情况下finally块也会不执行。

    try语句后面必须紧跟 catch 代码块或者 finally 代码块(也可以两者都存在)。当 try 中的代码发生错误时,如果存在catch代码块,那么它将会被将会被执行,如果只存在 finally 代码块的话,他将在 try 代码块执行完毕后执行,如果存在 catch 代码块和 finally 代码块的话,finally 代码块将在 catch 代码块执行完毕后执行。finally 主要的作用是不管 try 中是否发生错误,都要执行清理代码。现在我们通过一个例子来看一下:

    class Program
    {
        class Calculation
        {
            public int Division(int num)
            {
                try
                {
                    return 10 / num;
                }
                catch (DivideByZeroException ex)
                {
                    Console.WriteLine("0 不能作为除数");
                    return -999;
                }
            }
        }
    
        static void Main(string[] args)
        {
            Calculation calculation = new Calculation();
            calculation.Division(0);
            Console.Read();
        }
    }
    

    这段代码中 try 代码块会报 DivideByZeroException 错误,因为我们知道 除数不可能为0。但是这里不会影响程序的运行,因为报错的代码位于 try 中,try 将这个错误捕获到后,转给了 catch ,catch 对这个错误进行了处理。
    try 语句块后面的 catch 语句块可以有零个,也可以有 1个,也可以有多个。如果有多个 catch 语句块的话,应该遵循从小到大的顺序编写,所谓的从小到大就是,先捕获可以预见到异常例如上面例子中的 DivideByZeroException 异常,再捕获其他不可预见到的异常。我们把前面的代码改动一下,来看一下:

    class Program
    {
        class Calculation
        {
            public int Division(int num)
            {
                try
                {
                    return 10 / num;
                }
                catch (DivideByZeroException ex)
                {
                    Console.WriteLine("0 不能作为除数");
                    return -999;
                }
                catch (Exception ex)
                {
                    Console.WriteLine("其他异常");
                    return -999;
                }
            }
        }
    
        static void Main(string[] args)
        {
            Calculation calculation = new Calculation();
            calculation.Division(0);
            Console.Read();
        }
    }
    

    我们在上面的代码中增加了一个 catch (Exception ex) ,这个异常是所有异常的父类,它可以捕获所有任意类型的异常,因此需要把它放在所有 catch 语句块的后面,如果将它放在所有 catch 语句块的前面,将会无法通过编译。

    1. try…catch…finally 执行原理
      当抛出异常时,CLR会进行一个测试,判断当前是否在执行 try 中,并且能被 catch 捕获。如果是的话,抛出的错误将会传递个能兼容这个异常的 catch 代码块中,当 catch 处理完毕后将执行 try…catch 后面的语句,如果存在 finally 代码块,那么将会先执行 finally 代码块,再执行后面的语句。如果不是,CLR 会将这个错误向上抛出给 函数的调用者,并重复这个过程。

    注意:这里所说的 能兼容这个异常的 catch 代码块 指的是与这个异常的类型相等的类型,或者是 Exception

    一、catch 详解

    catch 代码块指定要补货的异常类型,这个异常类型必须是 Exception 或者它的子类。我在前面的小节也说过,Exception 捕获的是任何类型的错误,那么一定会造成在代码中滥用 Exception ,这里我就说一下在什么情况下需要使用到 Exception :

    1. 无论什么类型的异常,程序都可能从异常中恢复;
    2. 需要重新抛出异常,比如不在当前代码中处理,而是上层代码中处理,或者需要记录错误日志;
    3. 阻止出现异常时程序被终止。

    除了上述情况外,我们必须针对特定类型的异常,执行特定的 catch 处理异常,例如前面小节中,处理除数为0的 DivideByZeroException catch 代码块。如果代码存在多种异常的话,可以使用多个 catch 进行处理不同的异常,并且针对给定的异常,只有一个 catch 会执行。在需要多个 catch 的情况下,我建议将 Exception 这个 catch 作为最后一个异常,这样当异常不是已定义的某个具体异常时,最后这个异常可以捕获,防止程序被终止。我们再来看一下例子:

    static void Main(string[] args)
    {
        try
        {
            File.Delete(@"d:123.txt");
            Directory.Delete(@"D:hahaha");
        }
        catch (DirectoryNotFoundException ex)
        {
            Console.WriteLine("目录未找到");
        }
        catch (FileNotFoundException ex)
        {
            Console.WriteLine("文件不存在");
        }
        catch(Exception ex)
        {
            Console.WriteLine("其他异常!");
        }
    
        Console.ReadLine();
    }
    

    在这个例子中,一共有三个 catch 语句块,第一个是处理目录不存在异常的,第二个是处理文件不存在异常的,最后一个是用来处理其他异常的。

    二、catch 特殊用法

    1. 省略异常变量
      有时候我们并不需要知道异常的详情,这个时候我们就可以省略掉异常变量,代码如下:
    catch (DirectoryNotFoundException)
    {
      Console.WriteLine("目录未找到");
    }
    
    1. 省略异常类型
      与省略异常变量一样,有时候我们也不需要异常类型,这时我们就可以省略掉异常类型。当我们省略掉异常类型时,catch 块将会捕获所有类型的异常。代码如下:
    catch
    {
      Console.WriteLine("所有异常类型");
    }
    
    1. 过滤异常
      有些异常有可能是多种原因引起的,比如 WebException 异常,有可能是请求超时、请求地址不存在等问题引起的,但是我们只想处理超时引发的错误,这时我们只需在 catch 后面加上 when 关键字进行过滤即可,当符合过滤条件的话会执行 catch 中的处理语句,如果不符合将会执行后面符合异常条件的 catch 语句块,代码如下:
    catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
    {
       Console.WriteLine("超时");
    }
    

    三、finally

    finally 代码块在大部分情况下都会被执行的,不管try 中的代码是否执行完毕,是否有异常抛出。当如下三种情况时 finally 将会被执行:

    1. 执行完一个 catch 代码块后;
    2. return 语句跳出 try 代码块或者执行离开 try 代码块;
    3. try 代码块执行完毕。

    我刚才也说过,finally 在大部分情况下都会被执行,那么在什么情况下不会被执行呢?只有程序被强行终止或者在 try 代码块或 catch 代码块中存在无线死循环的情况下,finally 才不会被执行。一般情况下我们利用 finally 进行清理代码。我们看一下 finally 的例子:

    static void Main(string[] args)
    {
        StreamReader streamReader = File.OpenText(@"d:123.txt");
        try
        {
            if (streamReader.EndOfStream)
            {
                return;
            }
    
            Console.WriteLine(streamReader.ReadToEnd());
        }
        finally
        {
            if (streamReader != null)
            {
                streamReader.Dispose();
            }
        }
    
        Console.ReadLine();
    }
    

    上面代码中我们在 try中读取文件内容并输出,在 finally 将所占用的资源释放掉。

    四、特殊的finally

    有一种特殊的 finally ,它就是我们经常见到的 using,using 可以用来释放类中的非托管资源,比如数据库连接、文件处理等。这些类都实现了 IDisposable 接口,通过这个接口中的 Dispose 方法可以释放非托管资源。例如我们将前面读取文件内容的代码修改如下,同样可以实现上面 finally 中的效果:

    static void Main(string[] args)
    {
      using (StreamReader streamReader = File.OpenText(@"d:123.txt"))
      {
    
          if (streamReader.EndOfStream)
          {
              return;
          }
    
          Console.WriteLine(streamReader.ReadToEnd());
          Console.ReadLine();
      }
    }
    

    五、异常抛出

    1. 手动抛出异常
      异常不仅可以被 运行时 抛出,用户还可以手动抛出异常,例如我们手动抛出一个文件不存在异常,代码如下:
    static void Main(string[] args)
    {
       //..more code
       throw new FileNotFoundException();
       //..more code
    
    }
    

    在三元运算符中,也可以出现异常,代码如下:

    public int SpecificSize(int a, int b)
    {
        return a > b ? throw new Exception("a>b") :a+b;
    }
    
    1. 重新抛出异常
      加入我们不需要在当前方法中处理异常,我们就需要重新抛出异常,只需要在 catch 代码块中使用 throw 即可,代码如下:
    catch(DirectoryNotFoundException e)
    {
      throw e;
    }
    

    注意:当我们使用 throw e 抛出异常的话,调用方接收到异常的 stackfrace 属性将会发生改变,不会反映出原始异常,如果需要调用发接收原始异常的话,只需要 throw 即可。

    六、异常抛出的特殊情况

    1. 抛出更具体的异常
      有时候我们需要抛出更具体的异常,例如下面的例子:
    catch(Exception e)
    {
      throw new DivideByZeroException("除零异常",e);
    }
    

    上面代码中的这种情况就是抛出更具体的异常,这里需要注意的有两点:

    • 更具体的异常要比 catch 的异常类型范围要小;
    • 将异常变量作为参数传递给更具体的异常。
    1. 抛出抽象异常
      抛出抽象异常的目的,是因为需要穿越信任边界,防止信息泄露。

    下面我将列出常用的异常属性:

    异常属性 描述
    StackTrace 展现从异常发生点到 catch 代码块所有被调用的方法
    Message 异常的描述信息
    InnerException 引发外层异常的内层异常

    下面是常用的异常类型:

    异常类型 描述
    ArgumentException 参数异常
    ArgumentNullException 参数为null异常
    ArgumentOutOfRangeException 数值参数超出限定范围
    InvalidOperationException 操作不合理,例如未打开文件就直接读取文件内容
    NotSupportedException 不支持操作,例如修改只读属性
    NotImplementedException 所调用方法未实现
    ObjectDisposedException 所调用的对象已被释放
    NullReferenceException 空指针
  • 相关阅读:
    Spring 详解第三天
    Spring 详解第二天
    springmvc的运行流程分析
    Spring 详解第一天
    【Java面试题】40 你所知道的集合类都有哪些?主要方法?
    【Java面试题】39 Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?
    【Java面试题】38 Collection 和 Collections的区别
    【Java面试题】37 说出ArrayList,Vector, LinkedList的存储性能和特性
    【Java面试题】36 List、Map、Set三个接口,存取元素时,各有什么特点?
    【Java面试题】35 List, Set, Map是否继承自Collection接口?
  • 原文地址:https://www.cnblogs.com/gangzhucoll/p/12778172.html
Copyright © 2011-2022 走看看