常見的C#異常及其修復方法

語言: CN / TW / HK

常見的C#異常及其修復方法

如果您今天是依靠編寫的軟體來謀生,那麼您可能至少對異常的概念很熟悉。

Jeff Atwood曾經稱它們為“現代程式語言的基礎”。 異常 [1] 是現代軟體開發中常見且有用的結構,但有時它們也可能造成混亂。

那麼什麼是異常?更具體地說,C#異常的主要型別是什麼,以及如何使用它們?

今天的帖子將回答上述問題以及更多問題。我們將從“異常”的簡要定義開始,然後繼續解釋該結構的組成部分。最後,我們將概述最常見的C#異常以及如何處理它們。

讓我們開始吧。

有什麼異常?

異常是一種可用於處理錯誤的機制。就這麼簡單。在某些情況下,例如,C程式設計師將返回錯誤程式碼,而 Java [2] 或C#程式設計師都很有可能引發異常。

異常表示執行流程的突然中斷。一旦引發異常,執行就會停止。如果未處理異常,則應用程式崩潰。

但是,實際上您是如何做到的呢?您如何引發或捕獲異常?所有這些甚至意味著什麼?

這就是我們將在下一部分中詳細介紹的內容。

C#異常剖析

現在,我們將簡要介紹C#異常的情況。您將瞭解應該使用的主要關鍵字,這些關鍵字不僅可以捕獲和處理異常,還可以丟擲自己的異常。我們列表上的第一個是 try 關鍵字和塊。

try

異常處理解剖的第一部分是 try 塊。您可以使用它來嘗試執行一些可能引發異常的程式碼。考慮以下程式碼摘錄:

string content = string.Empty;

try

{

content = System.IO.File.ReadAllText(@"C:\file.txt");

}

上面的程式碼聲明瞭一個變數,併為其分配了空字串。然後,我們有了 try 塊。塊中的單行程式碼 try 使用該類中的 ReadAllText 靜態方法 System.IO.File 。我們擔心該路徑表示的檔案可能不存在,在這種情況下會引發異常。但是,當然,這還不夠。我們的程式碼不執行任何處理異常的操作。它甚至目前還沒有編譯!這是來自編譯器的訊息:

Expected catch or finally

該訊息是不言自明的。我們需要 catchfinally 程式碼塊,因為嘗試處理異常然後忘記執行處理部分沒有任何意義。讓我們開始吧。

catch

catch 程式碼塊使我們能夠實際處理異常。我們將使用更多程式碼擴充套件前面的示例。

看看這個:

static void Main(string[] args)

{

string content = string.Empty;



try

{

Console.WriteLine("First message inside try block.");

Console.WriteLine("Second message inside try block.");

content = System.IO.File.ReadAllText(@"C:\file.txt");

Console.WriteLine("Third message inside try block. This shouldn't be printed.");

}

catch

{

Console.WriteLine("First message inside the catch block.");

Console.WriteLine("Second message inside the catch block.");

}



Console.WriteLine("Outside the try-catch.");

Console.ReadLine();

}

如果執行上面的程式碼,則應看到以下內容:

First message inside try block.

Second message inside try block.

First message inside the catch block.

Second message inside the catch block.

Outside try-catch.

該路徑處的檔案不存在,因此將引發 System.FileNotFoundException 。發生這種情況時,執行流程將立即中斷。因此,將在try塊中列印“第三條訊息”的行。這不應該被列印。” 永遠不會被執行。

執行流程由 catch 塊執行。完成後,將控制權交還給main方法,然後列印 Outside try-catch 。並等待使用者輸入。

最後

在瞭解了 try 和之後 catch ,我們終於(沒有雙關語)準備好使用這個非常有用但經常被誤解的異常處理機制的一部分。那麼,什麼是 finally

finally 塊是一種確保將執行給定程式碼段的方式,無論是否引發異常。考慮下面的程式碼:

try

{

content = System.IO.File.ReadAllText(path);

Console.WriteLine("If you're reading this, no exception was thrown.");

}

catch (System.IO.FileNotFoundException e)

{

Console.WriteLine("If you're reading this, an exception was thrown.");

Console.WriteLine("Message: " + e.Message);

}

finally

{

Console.WriteLine("This will be printed, no matter what.");

}

那仍然是相同的示例,但是現在要簡單得多。注意最後的 finally 塊。如果執行此程式碼,則應該看到以下訊息:

If you're reading this, an exception was thrown.

Message: Could not find file 'C:\file.txt'.

This will be printed, no matter what.

Outside the try-catch.

現在讓我們模擬檔案的存在。註釋掉試圖從檔案中讀取的行,然後再次執行該應用程式。這是您現在應該看到的:

If you're reading this, no exception was thrown.

This will be printed, no matter what.

Outside the try-catch.

如您所見,在最近的場景中,沒有引發異常,因此該 catch 塊中沒有執行任何行。另一方面, finally 在兩種情況下都執行了該塊。

throw

當涉及到異常時,您將不會總是從別人那裡處理它們。您也可以自己丟擲異常。為此,您將使用 throw 關鍵字,然後是要引發的異常的類的例項化。以下程式碼舉例說明了這一點:

public ProductService(IProductRepository repository)

{

if (repository == null)

throw new ArgumentNullException();



this.repository = repository;

}

常見的.NET異常

以下是常見的.NET異常列表:

System.NullReferenceException

這是最著名的(甚至是臭名昭著的)異常之一。當您嘗試呼叫方法/屬性/索引器/等時,丟擲此異常。在包含空引用的變數(即,它不指向任何物件)。下面的程式碼將導致空引用異常:

Person p = people.Where(x => x.SSN == verifySsn).FirstOrDefault();

string name = p.Name;

在上面的示例中,我們過濾了將每個專案的SSN屬性與 verifySsnvariable 變數進行比較的序列。然後,我們使用該 FirstOrDefault() 方法從序列中僅提取第一項。如果序列不產生任何專案,則它將返回該型別的預設值。由於 Person 是引用型別,因此其返回值為null。

在下一行,我們嘗試 Name 在空引用上取消引用屬性。Boom!這將空引用異常。

這是通常不丟擲也不捕獲的異常。您不要丟擲它,因為它毫無意義。如果您想與程式碼的呼叫者交流,給定方法不接受null作為其引數的有效值,則使用的正確異常是 System.ArgumentNullException

您如何“修復”此異常?簡而言之,您必須對可為空性小心謹慎。如果您正在編寫將由第三方使用的任何程式碼(即使這些第三方是您的同事),則請認真考慮是否接受空引用作為有效值。

無論您做出什麼決定,都必須使該決定非常明確並記錄在案。您可以通過丟擲 System.ArgumentNullException ,例如,並在方法上使用XML文件標題來實現。

System.IndexOutOfRangeException

在應用程式程式碼通常不會丟擲或捕獲該異常的意義上,該異常與上一個異常類似。

那為什麼呢?

好吧,當您嘗試使用無效的索引值訪問陣列,列表或任何可索引序列中的元素時,將引發此異常。一個簡單的例子:

public static void PrintUrlSufix(string url)

{

var parts = url.Split('.');

Console.WriteLine(parts[2]);

}

上面的程式碼顯然希望使用 www.acme.com [3] 格式的URL 。但是,如果只是獲得 acme.com 怎麼辦?為此,如果得到 Hakuna Matata 怎麼辦?是的,沒錯: System.IndexOutOfBoundException 是的。

您如何避免遇到此異常?永遠不要把事情視為理所當然。永遠不要僅僅假設資料將採用正確的格式。做您的盡職調查和檢查的東西。

System.IO.IOException

此C#異常具有一個不言自明的名稱。這正是您的想法:這是IO操作期間發生錯誤時引發的異常。與前兩個異常不同,您可能會發現自己不時捕捉或丟擲其中一個。

IOException 類實際上是一些更具體的異常,例如:

DirectoryNotFoundException EndOfStreamException FileNotFoundException FileLoadException PathTooLongException

有關的文件, IOException 建議您儘可能使用更具體的異常,而不是更一般的異常。

System.Net.WebException

此異常與網路有關。如果使用 可插拔協議 [4] 訪問網路時發生錯誤,則丟擲該錯誤 [5] 處理此異常時,請記住驗證該 Response 屬性,該屬性將包含遠端主機返回的響應。

System.Data.SqlClient.SqlException

此異常與資料庫(特別是SQL Server)有關。SQL Server返回錯誤或警告時將引發該錯誤。該類具有一個稱為的屬性 Errors ,該屬性是一個包含 SqlError 該類的一個或多個例項的集合。依次包含有關發生的錯誤的詳細資訊。

System.StackOverflowException

當執行堆疊溢位時,丟擲此異常,這通常意味著遞迴出錯。該程式碼有太多的巢狀方法呼叫。事情就是這樣:這個異常是無法捕獲的-至少從.NET 2.0起就沒有-這意味著當丟擲該異常時,您幾乎沒有其他選擇。預設情況下,您的過程將被終止。

您應該做的而不是捕獲此異常的方法是編寫程式碼,以防止它首先發生。

System.OutOfMemoryException

可以說這是 最令人困惑的C#異常之一 [6] 。網上有很多資源 可以很好地闡明問題 [7] ,但是我將在此處提供一個簡短的版本。發生的情況是此異常不涉及可用的實體記憶體。

那麼,什麼時候丟擲此異常?

您知道何時要停車嗎,因為其他駕駛員未正確停車而不能停車嗎?如果您只需在停放的汽車之間新增所有可用空間,就足以容納您的車輛。但是目前不可能,因為它不是連續的區域。

當您獲得此記憶體時,這差不多發生了什麼。可能有很多可用的總記憶體,但是沒有連續的部分可以滿足所需的分配。實際上,例如,如果您嘗試將 StringBuilderMaxCapacity 屬性擴充套件到該屬性之外,則會發生此異常。

System.InvalidCastException

此異常也具有不言自明的名稱。當代碼由於未定義強制型別轉換而無法從一種型別轉換為另一種型別時,將引發該錯誤。以下程式碼將引發此型別的異常:

object o = "10";

int x = (int)o;

這是您通常不會捕獲的異常。相反,您將以不會發生的方式編寫程式碼。例如,以下程式碼在嘗試強制型別轉換之前進行型別檢查:

public override bool Equals (object obj )

{

if (!obj is Foo)

return false;



Foo other = (Foo)obj;

return this.bar == other.bar;

}

上面的程式碼可以使用簡化的 as 操作符 [8] ,在這裡我沒有進一步具體說明,作為一個練習留給讀者。無論如何,這就是問題:您實際上應該嘗試做的是避免問題,而不是首先進行轉換。利用泛型來防止陷入需要強制轉換的情況。

System.InvalidOperationException

像之前的許多其他異常一樣,這種異常通常是您不會發現的。相反,您應該做的是編寫不會發生的程式碼。考慮以下示例:

var numbers = new List<int> { 1, 3, 5 };

var firstGreaterThanFive = numbers.Where(x => x > 5).First();

當序列不產生任何結果時,將丟擲First LINQ擴充套件方法。如果您知道該序列有時可能不會產生結果-沒關係-您應該改用FirstOrDefault。在空序列上呼叫此方法時,將返回序列型別的預設值,而不是丟擲異常。

System.ObjectDisposedException

我們將在這篇文章中介紹的最後一個C#異常也屬於“不應該處理,請修復程式碼”類別。換句話說,這是開發人員錯誤。當您嘗試使用已處理的 IDisposable [9] 進行操作時,將引發此異常。

此當通常發生在開發者呼叫 DisposeClose 或其它類似方法和後來試圖訪問該物件的一個成員時。

如何成功的進行異常處理?

錯誤處理是軟體開發教學中經常被忽略的話題,出現異常有時非常不幸。如果沒有可靠的 錯誤處理策略 [10] ,您的應用程式有可能質量會不過關。

通過本文,我們希望通過定義異常的概念並對C#異常的主要型別進行快速概述,以幫助解決該問題。但本文並沒有涵蓋異常處理的全部。恰好相反,我認為這是一個機會,可以開始引導您對該主題的學習,並且永不停止學習和練習。

祝您能想出一種對您的應用程式有效的策略!

References

[1] 異常:  https://raygun.com/blog/java-exceptions-terminology/

[2] Java:  https://raygun.com/blog/java-exceptions-terminology/

[3] www.acme.com:  http://www.acme.com

[4] 可插拔協議:  https://docs.microsoft.com/dotnet/framework/network-programming/introducing-pluggable-protocols

[5] 。:  https://docs.microsoft.com/dotnet/framework/network-programming/introducing-pluggable-protocols

[6] 最令人困惑的C#異常之一:  https://stackoverflow.com/questions/1153702/system-outofmemoryexception-was-thrown-when-there-is-still-plenty-of-memory-fr

[7] 可以很好地闡明問題:  https://blogs.msdn.microsoft.com/ericlippert/2009/06/08/out-of-memory-does-not-refer-to-physical-memory/

[8] as 操作符:  https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/as

[9] IDisposable:  https://docs.microsoft.com/dotnet/api/system.idisposable?view=netframework-4.7.2

[10] 錯誤處理策略:  https://raygun.com/blog/errors-and-exceptions/