前言
用戶覺得異常是不好的,認為出現異常是寫的人的問題。
這是不全面,錯誤的出現並不總是編寫程序的人的原因,有時會因為應用程序的最終用戶引發的動作或運行代碼的環境而發生錯誤,比如你用android4去安裝現在的微信,或者說我們寫的android程序不需要兼容android4,需要的效果就是在android4上安裝然后崩潰。
我們編寫代碼的人需要做的是預測程序中出現的錯誤,並進行相應的處理,甚至有時候我們應該主動拋出異常。
無論編寫的代碼技術多么強,程序都必須處理可能發生的錯誤,比如說做客戶端的時候,突然網絡中斷了,那么后續操作需要中斷,我們需要做相應的處理;又比如說我們的文件出現權限問題,那么我們又需要處理。
這里介紹一下為什么我們調用io方法,如果是去讀取一個不存在的文件為什么拋出的是異常,而不是返回一個空的對象。
因為判斷是這樣子的,先把代碼貼出來:
string filePath = Environment.CurrentDirectory + "/content.txt";
Stream source = new FileStream(filePath, FileMode.Open, FileAccess.Read);
如果filePath 不存在那么將會拋出異常。
之所以拋出異常是這樣子的,FileStream是要去創建一個文件流,按理說文件流創建不成功,那么我的目的沒有達到那么就應該返回異常,異常就是我並沒有達到我使用的目的。
那么為啥我們調用indexof可以返回-1,表示我們沒有找到呢?是因為返回-1是約定,如果有約定那么可以不用拋出異常。
那么我們是否可以約定new FileStream返回為空呢?是不能的,因為存在歧義。文件流創建不成功,到底是文件不存在呢?還是權限問題呢?這些都是需要告訴我們的。
總結一下為什么我們需要主動拋出異常:
1.調用的目的沒有達到就應該拋出異常。
2.如果有約定就可以按照約定,如果約定存在歧義,那么不能使用約定。
好吧,那么開始正文吧。
正文
異常處理功能使用 try、catch 和 finally 關鍵字來嘗試執行可能失敗的操作、在你確定合理的情況下處理故障,以及在事后清除資源。
在try、catch、finally中,catch塊的運行方式值得關注:
如果引發異常的語句不在 try 塊內或者包含該語句的 try 塊沒有匹配的 catch 塊,
則運行時將檢查調用方法中是否有 try 語句和 catch 塊。 運行時將繼續調用堆棧,搜索兼容的 catch 塊。
在找到並執行 catch 塊之后,控制權將傳遞給 catch 塊之后的下一個語句。
舉個例子:
static void Main(string[] args)
{
FileStream sw = null;
try
{
sw = new FileStream(@"C:\test\test.txt", FileMode.Open, FileAccess.ReadWrite)
sw.Write(new byte[1]);
}
catch (DirectoryNotFoundException ex)
{
//文件目錄沒有找到異常
Console.WriteLine(ex);
}
catch (FileNotFoundException ex)
{
//文件沒有找到異常
Console.WriteLine(ex);
}
catch (IOException ex)
{
//io操作異常
Console.WriteLine(ex);
}
finally
{
sw?.Flush();
sw?.Close();
}
}
沒有找到找到相應的catch,那么會一步一步往下找。
首先是查看DirectoryNotFoundException ,然后是FileNotFoundException,只要是io操作出現問題都會是IOException。
那么我該如何知道DirectoryNotFoundException、FileNotFoundException、IOException他們的關系呢?
這時候就需要看圖了,圖不大,可以記下來。
如圖:
上面這些是常用的,可以說是必會的吧,下面介紹一下他們的作用。
異常 | 詳細 |
---|---|
ArgumentException | 方法參數異常 |
ArgumentNullException | 參數為空異常 |
ArgumentOutOfRangeException | 索引小於零或超出數組邊界時,嘗試對數組編制索引時引發 |
ArithmeticException | 算術運算期間出現的異常的基類,例如 DivideByZeroException 和 OverflowException。 |
OverflowException | 當在檢查的上下文中執行的算術、強制轉換或轉換運算導致溢出時引發的異常 |
StackOverflowException | 執行堆棧由於有過多掛起的方法調用而用盡時引發;通常表示非常深的遞歸或無限遞歸。 |
IOException | 發生I/O錯誤時引發的異常。 |
FileLoadException | 找到托管程序集但不能加載時引發的異常 |
FileNotFoundException | 文件沒有找到 |
EndOfSteamExcetion | 讀操作試圖超出流的末尾時引發的異常。 |
DriveNotFoundException | 當嘗試訪問的驅動器或共享不可用時引發的異常。 |
ApplicationException | 用作應用程序定義的異常的基類 |
TargetInvocationException | 由通過反射調用的方法引發的異常 |
CompositionException | 表示在 CompositionContainer 對象中進行組合期間發生一個或多個錯誤時引發的異常。 |
ChangeRejectedException | 一個指示部件在組合期間是否已遭拒絕的異常。 |
有些異常是我們寫代碼不應該去產生異常的,比如說ApplicationException作為基類的異常,如果出現這些異常一般是我們代碼寫的有問題,
而不是我們的主觀因素。應從 Exception 類(而不是 ApplicationException 類)派生自定義異常。 不應在代碼中引發 ApplicationException 異常,除非你
打算重新引發原始異常,否則不應捕獲 ApplicationException 異常。 這里舉個例子:TargetInvocationException的基類是ApplicationException,這個是
由通過反射調用的方法引發的異常。如果產生這個問題,可以想象是我們反射使用的有問題。同樣如果我們去寫反射的程序,我們不應該去catch這個,而是讓他直接
報錯,表示代碼就不應該這么寫,出錯然后去解決。
好吧,回到原問題上,假如沒有找到兼容性的catch塊那么會怎么樣呢?
那么實驗一下吧。
實驗如下:
會拋出異常的,所以我們寫代碼的時候需要確保最后一個一定能捕獲到異常的,如果sw.Write(new byte[1]);出現錯誤那么非托管資源就沒有釋放。
那么如何萬一我們沒有捕獲到異常也能是否非托管資源呢?使用using。
static void Main(string[] args)
{
try
{
using (var sw = new FileStream(@"C:\test\test.txt", FileMode.Open, FileAccess.ReadWrite))
{
sw.Write(new byte[1]);
}
}
catch (DirectoryNotFoundException ex)
{
//文件目錄沒有找到異常
Console.WriteLine(ex);
}
finally
{
}
}
這樣寫無論是否異常,那么都會釋放非托管資源。這個是可以實驗的,我把我的實驗貼一下。
上面的說明已經被關閉了,我們再次關閉的時候將會產生異常。這個using可以看下IL,就清楚其中的原理。
異常過濾器
異常過濾器是c#的東西,這里只是做簡單的介紹,后面異常代碼優化章節中會重點介紹。
public static bool ConsoleLogException(Exception e)
{
var oldColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Error:{0}",e);
Console.ForegroundColor = oldColor;
return false;
}
static void Main(string[] args)
{
int i = 10;
try
{
throw new TimeoutException("time out");
}
catch (Exception e) when(ConsoleLogException(e))
{
}
catch (TimeoutException e) when (i == 10)
{
}
finally
{
Console.ReadKey();
}
}
輸出結果:
上面說明一個問題,無論catch是否匹配,when都會執行。
同樣來做一個實驗,判斷when 如果不匹配也就是沒有catch塊執行那么會怎么樣?
static void Main(string[] args)
{
int i = 10;
try
{
throw new TimeoutException("time out");
}
catch (Exception e) when(ConsoleLogException(e))
{
}
catch (TimeoutException e) when (i == 9)
{
Console.WriteLine("enter timeoutexception");
}
finally
{
Console.ReadKey();
}
}
結果:
重新拋出異常
為什么要重新拋出異常呢?
static int GetValueFromArray(int[] array, int index)
{
try
{
return array[index];
}
catch (System.IndexOutOfRangeException ex)
{
System.ArgumentException argEx = new System.ArgumentException("Index is out of range", "index", ex);
throw argEx;
}
}
本來是要拋出IndexOutOfRangeException 改為了ArgumentException 這是為什么呢?
原因如下:用戶要調用的是我們的方法GetValueFromArray,傳入的是參數,方法沒有越界這么一說,而不是一個數組,所以我們要爭對我們的目的來確定我們拋出的異常。
用戶自定義異常
自定義異常水比較深,在此只做一個簡單的介紹,單獨一節補齊。
class CostumExcetion:Exception
{
public CostumExcetion(string message) : base(message)
{
}
}
調用如下:
static void Main(string[] args)
{
int i = 10;
try
{
throw new CostumExcetion("自定義異常");
}
catch (Exception e) when(ConsoleLogException(e))
{
}
catch (CostumExcetion e) when (i == 10)
{
Console.WriteLine(e.Message);
}
finally
{
Console.ReadKey();
}
}
調用者信息
現在又一個需要,知道try中出現錯誤,但是我需要知道是那一會出現錯誤,這個怎么破呢?
也就是說我們希望定位到行級,那么我們就需要調用者信息了。
在異常中,我們又很多方法調用一個方法,然后在這個方法中出現問題,我們需要知道是怎么報錯的,到底是哪個函數調用報錯的,這時候我們需要使用查詢到調用者信息。
舉個例子:
public class CallerInformationHelper
{
public void Log([CallerLineNumber]int line = -1, [CallerFilePath] string path = null, [CallerMemberName]string name = null)
{
Console.WriteLine((line<0)?"no line":"Line"+line);
Console.WriteLine((path==null)?"No file path":path);
Console.WriteLine((name==null)?"No Member name":name);
Console.WriteLine();
}
}
調用:
CallerInformationHelper helper = new CallerInformationHelper();
helper.Log();
結果:
結
因為異常整理較多,所以后續還有兩節整理。
1.異常注意事項,本章續。
2.解析盛派框架如何自定義異常類(以前做小程序的時候看過源碼)。
以上只是個人理解,如有問題請指出。如果學習,看文檔最佳。