问题一:try-catch-finally 中有return的情况,会如何执行
很多人都会纠结这么一个问题try-catch-finally中有return的情况,我自己总结如下:
1.1 如果是值类型的话
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace 含有return的测试 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 int j = Test(); 13 Console.WriteLine(j); 14 15 } 16 /// <summary> 17 /// 这个是测试return里面的是值类型就不会对他们有影响呢 18 /// </summary> 19 /// <returns></returns> 20 static int Test() 21 { 22 int i = 0; 23 24 try 25 { 26 27 return i; //为什么这里要加个return呢 28 } 29 catch (Exception) 30 { 31 32 i++; 33 return i; 34 35 } 36 finally 37 { 38 i = i + 2; 39 } 40 41 } 42 43 44 } 45 }
通过上面的代码可以看出,这里的finally执行了之后,对return返回没有影响 return返回结果还是0;
1.2 返回值是引用类型
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace 引用类型测试 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 List<string> strList = Test(); 13 foreach (var i in strList) 14 { 15 Console.WriteLine(i); 16 } 17 Console.ReadKey(); 18 } 19 private static List<string> Test() 20 { 21 List<string> strList = new List<string>(); 22 strList.Add("aa"); 23 strList.Add("bb"); 24 strList.Add("cc"); 25 try 26 { 27 //这里没有发生异常的 28 strList.Add("trytry"); 29 return strList; 30 } 31 catch (Exception ex) 32 { 33 strList.Add("zzz"); 34 return strList; 35 } 36 finally 37 { 38 strList.Add("yyy"); 39 } 40 } 41 42 } 43 }
输出结果如下:
通过以上两个例子可以总结: 如果是值类型的话,finally里面的改变不会对try或者catch中的return返回值造成影响。
如果是引用类型的话,finally里面的改变会对try或者catch中的return返回值造成影响(除了String外)。
造成这个结果的主要原因就是return返回的是栈中的地址。
问题二:多个Try catch finally的执行顺序。
try这个执行顺序不说了。
catch块:如果Try块中的代码没有抛出异常的话呢,CLR永远都不会执行它的任何Catch块
如果是不会报异常的话,线程就会跳过catch块,直接执行finally块(如果有的话)。
但是如果try块里面报了异常的话呢。并且Catch也能捕捉到这个异常(第一层Catch就可以捕捉到)。那就是try catch finally的顺序执行。
假设第一层Catch没有匹配到,就会继续往上面的抛,直到Catch可以匹配到。如果没有匹配到Catch就直接报异常了,程序崩了(如果没有捕捉到异常的话呢,finally的语句是不会执行的。如果有finally的话,最后面会去执行finally)。
如果catch第一层没有捕捉到,在第二层也没有捕捉到,在第三层的Catch里面捕捉到这个异常的话呢,会在进入catch之前先之前第一层的finally(如果有),第二层finally执行完了,才去第三层执行对应的catch里面的内容:如下面代码所示:
static void MethodFour() { try { throw new NotImplementedException(); } catch(DivideByZeroException ex) { } finally { Console.WriteLine("最里面的 finally"); } }
上面这个方法是不会匹配到异常,异常会往上面抛(它的调用如下)
try { MethodFour(); //这里输出的顺序是先finally,再Exception。 } catch(Exception ex) { //异常会往上抛,在它匹配到这个异常的时候. Console.WriteLine($"Exception"); } finally { Console.WriteLine("最外面的 Finally"); }
具体结果如何,大家可以动手试试。
问题三:很多人说Catch要指定先具体的异常类型,最后再写一个Exception。
try块:一般是可能报异常的。每一个try至少有一个catch块或finally块。
catch块:包含的是响应异常需要执行的代码。如果一个try块里面没有造成异常,CLR永远不会执行它的任何catch块。线程将跳过所有catch块,直接执行finally块(如果有的话)。
catch关键字后的圆括号中的表达式称为捕捉类型。C#要求捕捉类型必须是System.Exception或者它的派生类型。
有了这两个基础之后,再解释问题一,CLR是自上而下搜索匹配catch块,所以应该将较具体的异常放在顶部。也就是说首先要出现的是派生度最大的异常类型,接着是它们的基类型(如果有的话),最后是System.Exception(或者是没有指定任何捕捉类型的catch块)。
那么问题又来了,很多时候,我们是直接一个try{ ...}catch(excepiton ex){ ... } ,和我们上面说的做法(写到具体的异常类型)有多大区别呢。
直接上例子:
static void MethodOne() { try { int i = 0; int j = 10 / i; } catch(FormatException ex) { //假设这里多一层比较,再匹配到 } catch (DuplicateWaitObjectException ex) { //再多一层比较 } catch(DivideByZeroException ex) { // Console.WriteLine("DivideByZeroException异常"); } catch(Exception ex) { Console.WriteLine("Exception ONE"); } }
static void MethodTwo() { try { int i = 0; int j = 10 / i; } catch (Exception ex) { // Console.WriteLine("Exception TWO"); } }
上面的例子,跑出来的结果说明(大家动手跑一下),methodone(匹配到具体异常的)要比methondtwo(直接exception)要高效。
问题四:Throw和Throw ex区别
每次看到别人写的代码有时候Throw; 有时候又Throw ex;总是充满好奇,他们为什么要这么写,这样写有啥区别呢。下面我们一起来看看究竟。
1、先说Throw ex会重置异常,异常堆栈信息会重新抛出,这样就会影响我们根据异常信息来定位问题。
2、Throw 会传递异常,不会重置异常。
static void TestMethod() { try { Console.WriteLine($"ThreadId{Thread.CurrentThread.ManagedThreadId}"); TestThrow(); } catch (Exception ex) { Console.WriteLine("除以零报异常了"); //throw ex;//把异常信息向上抛了。 throw ex; } } static void TestThrow() { var t = 0; var res = 10 / t; }
问题五:对于try catch finally对性能影响一说
//当异常被重置的话呢。 try { } catch(FormatException ex) { //这里写一个日志。logHelp.write(ex) throw new Exception("格式化异常"); } catch(DivideByZeroException ex) { //这里写一个日志。logHelp.write(ex) throw new Exception("尝试除以零"); } catch(IndexOutOfRangeException ex) { //这里写一个日志。logHelp.write(ex) throw new Exception("索引溢出"); } catch(Exception ex) { //这里写一个日志。logHelp.write(ex) throw new Exception("其他异常"); } finally { //资源清理 }
从上面这段代码,可以看到try catch finally给我们的感觉就是代码结构清晰,分门别类。
异常的处理好处有如下(个人觉得)
1、代码可读性很好,正常的逻辑和异常处理,资源清理都分开。
2、系统可靠性提升了。
3、链式调用方法可以很轻松的实现了。
关于性能这一说法:
一旦异常被抛出去的话,函数也会跟着return,而程序在执行时候,需要处理函数栈的上下文,如果函数栈比较深的话,性能就更加受影响了。从另外一方面来说,如果你的程序报异常了,就应该要停止执行了。程序本来就应该在没有异常的情况下执行的(所以你大部分情况下,程序是不会抛出异常的,对性能的影响也就不那么大了)。