重學c#系列——異常續[異常注意事項](七)
阿新 • • 發佈:2020-08-03
### 前言
對上節異常的補充,也可以說是異常使用的注意事項。
### 正文
#### 減少try catch的使用
前面提及到,如果一個方法沒有實現該方法的效果,那麼就應該丟擲異常。
如果有約定那麼可以按照約定,如果約定有歧義,那麼還是要丟擲異常。
我們知道使用try catch 其實是要消耗效能的,那麼是否能避免使用try catch呢?或者減少使用try catch呢?
我們使用api的時候,知道http制定了一套錯誤碼,那麼我們是否能使用錯誤碼返回來做一個約定的呢?
答案是否定的,且不論我們的業務的不同,錯誤碼制定的困難,單是方法裡面使用錯誤碼偶和性就非常大。
既然無法通過約定一套錯誤碼來解決,那麼是否可以先驗證該方法能否執行成功呢?
``` csharp
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];
}
}
```
呼叫:
``` csharp
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,這樣寫是否程式碼風格不統一呢?還是說他們之間有區別呢?
``` csharp
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,所以會這樣寫:
``` csharp
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,反覆檢查,捕獲異常,從而不會中斷程式。
### 結
這一章是對上一節的補充,還有很多細節是無法寫完的,就到此吧,後續是一個自定義異常的例子。
以上只是個人整理,如有問題,請指出。最後卑微的說一句:跪求大佬指點 。
![](https://img2020.cnblogs.com/blog/1289794/202008/1289794-20200803161225300-16906534