重學c#系列——異常續[異常注意事項](七)

團隊buff工具人發表於2020-08-03

前言

對上節異常的補充,也可以說是異常使用的注意事項。

正文

減少try catch的使用

前面提及到,如果一個方法沒有實現該方法的效果,那麼就應該丟擲異常。

如果有約定那麼可以按照約定,如果約定有歧義,那麼還是要丟擲異常。

我們知道使用try catch 其實是要消耗效能的,那麼是否能避免使用try catch呢?或者減少使用try catch呢?

我們使用api的時候,知道http制定了一套錯誤碼,那麼我們是否能使用錯誤碼返回來做一個約定的呢?

答案是否定的,且不論我們的業務的不同,錯誤碼制定的困難,單是方法裡面使用錯誤碼偶和性就非常大。

既然無法通過約定一套錯誤碼來解決,那麼是否可以先驗證該方法能否執行成功呢?

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];
	}
}

呼叫:

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,這樣寫是否程式碼風格不統一呢?還是說他們之間有區別呢?

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,所以會這樣寫:

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,反覆檢查,捕獲異常,從而不會中斷程式。

這一章是對上一節的補充,還有很多細節是無法寫完的,就到此吧,後續是一個自定義異常的例子。

以上只是個人整理,如有問題,請指出。最後卑微的說一句:跪求大佬指點 。

相關文章