- 1 前處理器
- 1.1 簡介
- 1.1.1 定義
- 1.1.2 前處理器指令列表
- 1.2 指令示例詳解
- 1.2.1 #define 和 #undef 前處理器
- 1.2.2 條件指令:#if, #elif, #else 和 #endif
- 1.2.3 綜合示例
- 1.1 簡介
- 2 異常處理
- 2.1 簡介
- 2.1.1 定義
- 2.1.2 異常類
- 2.2 異常處理
- 2.2.1 常規處理
- 2.2.2 不指定具體異常
- 2.2.2.1 catch 中不指定異常型別
- 2.2.2.2 throw 不指定異常物件
- 2.2.2.3 throw ex 與 throw 的區別
- 2.2.3 使用using
- 2.3 自定義異常
- 2.1 簡介
1 前處理器
1.1 簡介
1.1.1 定義
前處理器指令(Preprocessor Directives
)指導編譯器在實際編譯開始之前對資訊進行預處理。
透過這些指令,可以控制編譯器如何編譯檔案或編譯哪些部分。常見的前處理器指令包括條件編譯、宏定義等。
所有的前處理器指令都是以 #
開始,且在一行上,只有空白字元可以出現在前處理器指令之前。前處理器指令不是語句
,所以它們不以分號 ; 結束
。
C# 編譯器沒有一個單獨的前處理器,但指令被處理時就像是有一個單獨的前處理器一樣。在 C# 中,前處理器指令用於在條件編譯中起作用。與 C 和 C++ 不同的是,它們不是用來建立宏。一個前處理器指令必須是該行上的唯一指令。
使用前處理器指令特點:
- 提高程式碼可讀性:使用
#region
可以幫助分隔程式碼塊,提高程式碼的組織性。 - 條件編譯:透過
#if
等指令可以在開發和生產環境中編譯不同的程式碼,方便除錯和釋出。 - 警告和錯誤:透過
#warning
和#error
可以在編譯時提示開發人員注意特定問題
1.1.2 前處理器指令列表
指令 | 描述 |
---|---|
#define | 定義一個符號,可以用於條件編譯 |
#undef | 取消定義一個符號 |
#if | 開始一個條件編譯塊,如果符號被定義則包含程式碼塊 |
#elif | 如果前面的 #if 或 #elif 條件不滿足,且當前條件滿足,則包含程式碼塊 |
#else | 如果前面的 #if 或 #elif 條件不滿足,則包含程式碼塊 |
#endif | 結束一個條件編譯塊 |
#warning | 生成編譯器警告資訊 |
#error | 生成編譯器錯誤資訊 |
#region | 標記一段程式碼區域,可以在IDE中摺疊和展開這段程式碼,便於程式碼的組織和閱讀 |
#endregion | 結束一個程式碼區域 |
#line | 更改編譯器輸出中的行號和檔名,可以用於除錯或生成工具的程式碼 |
#pragma | 用於給編譯器傳送特殊指令,例如禁用或恢復特定的警告 |
#nullable | 控制可空性上下文和註釋,允許啟用或禁用對可空引用型別的編譯器檢查 |
1.2 指令示例詳解
1.2.1 #define 和 #undef 前處理器
#define
用於定義符號(通常用於條件編譯),#undef
用於取消定義符號。
#define
允許定義一個符號,這樣,透過使用符號作為傳遞給 #if
指令的表示式,表示式將返回 true
下面的程式說明了這點:
#define PI
using System;
namespace PreprocessorDAppl
{
class Program
{
static void Main(string[] args)
{
#if (PI)
Console.WriteLine("PI is defined");
#else
Console.WriteLine("PI is not defined");
#endif
Console.ReadKey();
}
}
}
1.2.2 條件指令:#if, #elif, #else 和 #endif
條件指令用於測試符號是否為真。如果為真,編譯器會執行 #if
和下一個指令之間的程式碼。
常見運算子有:== (等於),!= (不等於),&& (與),|| (或)
也可以用括號把符號和運算子進行分組。條件指令用於在除錯版本或編譯指定配置時編譯程式碼。一個以 #if
指令開始的條件指令,必須顯示地以一個 #endif
指令終止。
#define DEBUG
#if DEBUG
Console.WriteLine("Debug mode");
#elif RELEASE
Console.WriteLine("Release mode");
#else
Console.WriteLine("Other mode");
#endif
例項
#define DEBUG
#define VC_V10
using System;
public class TestClass
{
public static void Main()
{
#if (DEBUG && !VC_V10)
Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && VC_V10)
Console.WriteLine("VC_V10 is defined");
#elif (DEBUG && VC_V10)
Console.WriteLine("DEBUG and VC_V10 are defined");
#else
Console.WriteLine("DEBUG and VC_V10 are not defined");
#endif
Console.ReadKey();
}
}
1.2.3 綜合示例
#define DEBUG
#if DEBUG
Console.WriteLine("Debug mode");
#elif RELEASE
Console.WriteLine("Release mode");
#else
Console.WriteLine("Other mode");
#endif
#warning This is a warning message
#error This is an error message
#region MyRegion
// Your code here
#endregion
#line 100 "MyFile.cs"
// The next line will be reported as line 100 in MyFile.cs
Console.WriteLine("This is line 100");
#line default
// Line numbering returns to normal
#pragma warning disable 414
private int unusedVariable;
#pragma warning restore 414
#nullable enable
string? nullableString = null;
#nullable disable
2 異常處理
2.1 簡介
2.1.1 定義
異常是在程式執行期間出現的問題。C#
中的異常是對程式執行時出現的特殊情況的一種響應,異常提供了一種把程式控制權從某個部分轉移到另一個部分的方式。C# 異常處理時建立在四個關鍵詞之上的:try
、catch
、finally
和 throw
。
try
:一個try
塊標識了一個將被啟用的特定的異常的程式碼塊。後跟一個或多個 catch 塊。catch
:程式透過異常處理程式捕獲異常,catch
關鍵字表示異常的捕獲。finally
:finally
塊用於執行給定的語句,不管異常是否被丟擲都會執行,如果開啟一個檔案,不管是否出現異常檔案都要被關閉。throw
:當問題出現時,程式丟擲一個異常。使用throw
關鍵字來完成。
2.1.2 異常類
C# 異常是使用類來表示的。C# 中的異常類主要是直接或間接地派生於 System.Exception
類。
System.ApplicationException
類支援由應用程式生成的異常。所以程式設計師定義的異常都應派生自該類。
System.SystemException
類是所有預定義的系統異常的基類。
派生自 System.SystemException
類的預定義的異常類:
異常類 | 描述 |
---|---|
System.IO.IOException | 處理 I/O 錯誤 |
System.IndexOutOfRangeException | 處理當方法指向超出範圍的陣列索引時生成的錯誤 |
System.ArrayTypeMismatchException | 處理當陣列型別不匹配時生成的錯誤 |
System.NullReferenceException | 處理當依從一個空物件時生成的錯誤 |
System.DivideByZeroException | 處理當除以零時生成的錯誤 |
System.InvalidCastException | 處理在型別轉換期間生成的錯誤 |
System.OutOfMemoryException | 處理空閒記憶體不足生成的錯誤 |
System.StackOverflowException | 處理棧溢位生成的錯誤 |
2.2 異常處理
2.2.1 常規處理
C# 以 try 和 catch 塊的形式提供了一種結構化的異常處理方案。使用這些塊,把核心程式語句與錯誤處理語句分離開。
這些錯誤處理塊是使用 try、catch 和 finally 關鍵字實現的。下面是一個當除以零時丟擲異常的例項:
using System;
namespace ErrorHandlingApplication
{
class DivNumbers
{
int result;
DivNumbers()
{
result = 0;
}
public void division(int num1, int num2)
{
try
{
result = num1 / num2;
}
catch (DivideByZeroException e)
{
Console.WriteLine("Exception caught: {0}", e);
}
finally
{
Console.WriteLine("Result: {0}", result);
}
}
static void Main(string[] args)
{
DivNumbers d = new DivNumbers();
d.division(25, 0);
Console.ReadKey();
}
}
}
執行結果:
Exception caught: System.DivideByZeroException: Attempted to divide by zero.
at ...
Result: 0
2.2.2 不指定具體異常
用法 | 特點 | 使用場景 |
---|---|---|
catch | 不指定異常型別 | 捕獲所有異常,但無法訪問異常物件 |
throw | 重新丟擲當前異常,保留原始堆疊資訊 | 捕獲異常後記錄日誌或執行其他操作再丟擲 |
throw ex | 丟擲捕獲的異常,但丟失原始堆疊資訊 | 不推薦,除非需要自定義異常或修改異常資訊 |
2.2.2.1 catch 中不指定異常型別
在 catch 中不指定異常型別,這種方式會捕獲所有型別的異常
。
語法如下:
try
{
// 可能引發異常的程式碼
}
catch
{
// 捕獲所有異常
Console.WriteLine("發生了一個異常");
}
特點:
- 捕獲所有異常:
catch
塊不指定異常型別時,任何異常都會被捕獲。 - 不提供異常上下文:因為沒有捕獲具體的異常物件,無法訪問異常的詳細資訊(如訊息、堆疊跟蹤等)。
- 使用場景:適合處理一些通用的異常情況,但不建議濫用,因為可能會掩蓋真正的問題。
2.2.2.2 throw 不指定異常物件
在 throw
後面可以不指定異常物件。這種方式會重新丟擲當前捕獲的異常,並保留原始異常的堆疊跟蹤資訊。
語法如下:
try
{
// 可能引發異常的程式碼
}
catch (Exception)
{
Console.WriteLine("捕獲到異常,但繼續丟擲");
throw; // 重新丟擲當前異常
}
特點:
- 重新丟擲原始異常:保留原始異常的堆疊跟蹤資訊。
- 使用場景:適用於需要在捕獲異常後進行一些處理(如記錄日誌)然後繼續將異常傳遞給上層呼叫者。
2.2.2.3 throw ex 與 throw 的區別
在 catch
塊中,既可以使用 throw;
重新丟擲當前異常,也可以使用 throw ex;
丟擲捕獲的異常物件。
複製程式碼
try
{
throw new InvalidOperationException("測試異常");
}
catch (Exception ex)
{
Console.WriteLine("捕獲異常並重新丟擲");
throw; // 重新丟擲,保留原始堆疊資訊
// throw ex; // 丟擲 ex,但堆疊資訊會被重置
}
區別:
throw
:重新丟擲當前異常,保留原始異常的堆疊跟蹤。throw ex
:丟擲捕獲的異常物件,但會重置堆疊跟蹤資訊,使得原始異常的來源資訊丟失。
2.2.3 使用using
using
語句確保在塊結束時自動釋放資源,即使發生異常也是如此。這在處理實現了 IDisposable
介面的物件時特別有用,例如檔案流、資料庫連線等。
using (ResourceType resource = new ResourceType())
{
// 使用資源的程式碼
}
在這個語法中,ResourceType
必須實現 IDisposable
介面。using
語句會在塊結束時自動呼叫 Dispose
方法,釋放資源。
使用 using 語句處理檔案讀取寫入的示例:
using System;
using System.IO;
public class Program
{
public static void Main()
{
string inputPath = "input.txt";
string outputPath = "output.txt";
// C# 8.0 之前版本
using (StreamReader reader = new StreamReader(inputPath))
using (StreamWriter writer = new StreamWriter(outputPath))
// 適用於 C# 8.0 及以上版本
//using (StreamReader reader = new StreamReader(inputPath),writer = new StreamWriter(outputPath))
{
string content = reader.ReadToEnd();
writer.Write(content);
}
}
}
2.3 自定義異常
自定義的異常類是派生自 ApplicationException
類。下面的例項演示了這點:
using System;
namespace UserDefinedException
{
class TestTemperature
{
static void Main(string[] args)
{
Temperature temp = new Temperature();
try
{
temp.showTemp();
}
catch(TempIsZeroException e)
{
Console.WriteLine("TempIsZeroException: {0}", e.Message);
}
Console.ReadKey();
}
}
}
public class TempIsZeroException: ApplicationException
{
public TempIsZeroException(string message): base(message)
{
}
}
public class Temperature
{
int temperature = 0;
public void showTemp()
{
if(temperature == 0)
{
throw (new TempIsZeroException("Zero Temperature found"));
}
else
{
Console.WriteLine("Temperature: {0}", temperature);
}
}
}