try语句提供了一种捕获程序发生异常的机制。try语句有三种形式:
1、一个try块儿后跟一个或多个catch块儿
2、一个try块儿后跟一个finally块儿
3、一个try块儿后跟一个或多个catch块儿,最后再跟一个finally块儿。(最常用)
例子:声明一个Caculator类型,并定义一个Add()方法,调用这个Add()方法并传入该方法需要的两个string类型的参数,即可求出两数之和。
class Program { static void Main(string[] args) { Caculator caculator = new Caculator(); int result= caculator.Add("abc","123"); Console.WriteLine(result); } } class Caculator { public int Add(string arg1,string arg2) { int a = int.Parse(arg1); int b = int.Parse(arg2); return a + b; } }
1):执行上面的代码程序会出现下面的错误信息。
Unhandled exception. System.FormatException: Input string was not in a correct format. at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type) at System.Number.ParseInt32(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info) at System.Int32.Parse(String s) at trycatchstatement.Caculator.Add(String arg1, String arg2) in.....
这并不是我们预期想要的结果。在上述信息中可以看到,我们要认识的第一个异常:格式化异常(System.FormatException);冒号后面是对异常的描述:输入的字符串不是正确的格式。
2):为了规避程序发生的格式化异常的错误,我们使用try..catch来改写代码。
在使用try语句的时候要注意,哪里出错就将哪里的代码包括进try块儿中。这里我们很容易能够判断出来:int的Parse方法会出现FormatException。
public int Add(string arg1, string arg2) { int a = 0; int b = 0; try { a = int.Parse(arg1); b = int.Parse(arg2); } catch { Console.WriteLine("参数输入有误"); } return a + b; }
再次运行程序:
参数输入有误 0
分析:由于参数arg1是一个"abc"值,所以代码在执行到a=int.Parse(arg1)这一行时会产生FormatException;但是由于这段代码包含在了try块儿中,所以当异常发生时程序就会进入到catch块儿中进行处理。这里我们的处理,就是打印在控制台中一行自己定义的错误提示。
3):上面的例子中,catch后面什么也没写,这种属于捕捉通用类型的异常;但catch后面其实可以跟一个错误类型,来捕捉特定类型的异常。
由于我们从前面控制台中输出的异常信息中,可以得知我们的代码会发生FormatException。所以我们可以这样改写程序。
public int Add(string arg1, string arg2) { int a = 0; int b = 0; try { a = int.Parse(arg1); b = int.Parse(arg2); } catch(FormatException) { Console.WriteLine("发生了类型转换异常"); } return a + b; }
运行结果:
发生了类型转换异常 0
4):其实到此为止,我们的程序并不是完全没有问题的;比如程序调用时这样写:
static void Main(string[] args) { Caculator caculator = new Caculator(); int result = caculator.Add("99999999999999999", "123"); Console.WriteLine(result); }
运行结果:
Unhandled exception. System.OverflowException: Value was either too large or too small for an Int32. at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type) at System.Number.ParseInt32(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info) at System.Int32.Parse(String s) at trycatchstatement.Caculator.Add(String arg1, String arg2) in....
分析:从控制台输出的错误信息可以知道我们又遇到了一种新的异常--溢出异常(System.OverflowException);异常详细信息是:对于一个Int32类型的变量来说值过大或者过小。而观察我们传入的999..999可以知道我们传入的值如果转换成数值就会超出int类型能够容纳的范围,所以我们还要追加catch块儿,来把OverflowException考虑进去。
public int Add(string arg1, string arg2) { int a = 0; int b = 0; try { a = int.Parse(arg1); b = int.Parse(arg2); } catch(FormatException) { Console.WriteLine("发生了类型转换异常"); } catch (OverflowException) { Console.WriteLine("发生了溢出异常"); } return a + b; }
运行结果:
发生了溢出异常 0
分析:上面的代码其实是为了练习try块儿后面跟多个catch块儿的情景。其实我们没有把可能发生的异常全部考虑进去,只是达到了联系效果。
5):其实在catch后面除了跟异常的类型外,还可以在类型后面跟异常的变量名,目的是当捕捉到异常的时候,可以使用异常变量所包含的信息。
class Program { static void Main(string[] args) { Caculator caculator = new Caculator(); int result = caculator.Add(null, "123"); Console.WriteLine(result); } } class Caculator { public int Add(string arg1, string arg2) { int a = 0; int b = 0; try { a = int.Parse(arg1); b = int.Parse(arg2); } catch(FormatException fe) { Console.WriteLine(fe.Message); } catch (OverflowException oe) { Console.WriteLine(oe.Message); } catch (ArgumentNullException ae) { Console.WriteLine(ae.Message); } return a + b; } }
运行结果:
Value cannot be null. (Parameter 's') 0
6):至此我们还没用到finally。当程序顺利离开try语句时,如果我们写的有finally块儿,那么finally块儿是一定会执行的。
举例:模拟一个在try语句中使用了return语句的操作,(一般情况下我们会认为程序执行到return语句时就会结束),但是从下面代码的输出可以观察到finally块儿还是会被执行的。
public int Add(string arg1, string arg2) { bool hasError = false; int a = 0; int b = 0; try { a = int.Parse(arg1); b = int.Parse(arg2); return a + b; } catch (FormatException fe) { Console.WriteLine(fe.Message); hasError = true; return -1; } catch (OverflowException oe) { Console.WriteLine(oe.Message); hasError = true; return -1; } catch (ArgumentNullException ae) { Console.WriteLine(ae.Message); hasError = true; return -1; } finally { if (hasError) { Console.WriteLine("发生了异常"); } else { Console.WriteLine("执行结束"); } } }
运行结果:
Value cannot be null. (Parameter 's') 发生了异常 -1
分析:上面程序实现的逻辑是:当程序发生异常时,由对应的catch块儿进行处理后 return一个-1。然后再在finally中进行一个程序是否有错误发生的判断进而向用户显示程序执行后得的结果是否是正常合理的值。从上面的输出可以看出即使在程序发生了异常,返回了-1的情况下,finally块儿仍然执行了。
例子:continue语句,能阻止finally块儿执行么?(下面程序中的CountNumber方法的目的是统计数组中符合条件的元素有几个)
class Program { static void Main(string[] args) { Caculator caculator = new Caculator(); //int result = caculator.Add(null, "123"); //Console.WriteLine(result); string[] arr = { "999", "-1", "123", "12.3","-0.2" }; int r= caculator.CountNumber(arr); Console.WriteLine(r); } } class Caculator { public int CountNumber(string[] arr) { int result = 0; foreach (var item in arr) { try { int i = int.Parse(item); if (i < 0) continue; else result++; } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { Console.WriteLine($"{item}"); } } return result; } }
运行结果:
999 -1 123 Input string was not in a correct format. 12.3 Input string was not in a correct format. -0.2 2
分析:可以看出程序进入了catch块儿或者执行了continue语句,不能阻止inally块儿执行。
下面是程序执行时的调试过程:
III:将异常传播到try语句之外。
如何将异常传播到try语句之外呢?我们需要借助throw关键字。程序修改如下。
class Program { static void Main(string[] args) { Caculator caculator = new Caculator(); //int result = caculator.Add(null, "123"); //Console.WriteLine(result); try { string[] arr = { "999", "-1", "123", "12.3", "-0.2" }; int r = caculator.CountNumber(arr); Console.WriteLine(r); } catch (Exception ex) { Console.WriteLine(ex.Message); } } } class Caculator { public int CountNumber(string[] arr) { int result = 0; foreach (var item in arr) { try { int i = int.Parse(item); if (i < 0) continue; else result++; } catch (Exception ex) { throw ex; } finally { Console.WriteLine($"{item}"); } } return result; } }
运行结果:
999 -1 123 12.3 Input string was not in a correct format.
分析:当我们通过throw将异常传播到try之外后, 那么异常该怎么处理呢?谁调用谁处理。所以我们在Main()方法中,调用caculator.CountNumber(arr)的位置使用try语句包括起来,这样程序就不会报错了。此外,可以观察到12.3还是被输出了--这表明了在CountNumber方法中finally块儿还是被执行了。下图是执行过程。
总结:
1、在catch块儿中,我们经常用Exception类型进行异常的捕获,它是一个通用的异常类,各种异常都会被捕捉到;
2、finally块儿在上面我们讲到的情况下都会被执行,使用finally块儿通常是为了执行一些必须要执行的操作:比如释放一些被Lock的资源、比如虽然程序表面上没有因异常而死掉但确实发生了异常时需要给用户有好的提示等;
3、我们在写程序的时候要尽可能多地在可能出现异常的地方去使用try...catch语句去捕捉异常,以防异常没有被捕捉到引起程序崩溃,被测试小姐姐给自己提BUG。
以上便是try语句的学习总结,记录下来以便以后查阅。