C# 語法看似熟悉 (C/C++/Java 風格),卻藏著許多「只差一行、行為大不同」的細節。本文整理 10 個新手最易踩的坑,附上 IL 層級解析與最佳實務,助你寫出更穩健、可維護的代碼。
@ 逐字字串字面值
// 一般字串:需雙反斜線跳脫
string path1 = "C:\\Projects\\MyApp\\bin\\Debug";
// @ 逐字字串:反斜線不跳脫,換行字面保留
string path2 = @"C:\Projects\MyApp\bin\Debug";
// @ 字串中要輸入雙引號:寫兩個雙引號
string json = @"{ ""name"": ""John"", ""age"": 30 }";
// 變數名稱可用 @ 前綴 (避開關鍵字)
int @class = 1; // 合法,但不建議濫用
補充:@$"" 或 $@"" 結合插值與逐字 (@"C:\{folder}\{file}")。
Convert.ToXxx vs value.ToString() / Parse| 方法 | null 處理 | 例外 | 適用場景 |
|---|---|---|---|
Convert.ToInt32(obj) | 回傳 0 | 格式錯誤拋 FormatException | 物件可能為 null、多型來源 |
int.Parse(str) | 拋 ArgumentNullException | 格式錯誤拋 FormatException | 確定非 null 字串、效能略優 |
int.TryParse(str, out int v) | 回傳 false | 不拋例外 | 首選:高效能、無例外控制流 |
obj.ToString() | 拋 NullReferenceException | 無 | 物件確定非 null、自訂格式 |
Convert.ToString(obj) | 回傳 string.Empty | 無 | 物件可能為 null、安全轉字串 |
口訣:轉型用 TryParse、物件轉字串用 Convert.ToString、確定非 null 才用 .ToString()。
switch 不支援貫穿 — 必須 break/return/goto case
// 錯誤:C# 編譯錯誤 CS0163
switch (x) { case 1: Do1(); case 2: Do2(); break; }
// 正確:明確 break
switch (x) { case 1: Do1(); break; case 2: Do2(); break; }
// 正確:goto case (刻意貫穿)
switch (x)
{
case 1: Do1(); goto case 2;
case 2: Do2(); break;
default: DoDefault(); break;
}
// C# 7.0+ 模式比對 (更強大)
switch (obj)
{
case int i when i > 0: return "positive int";
case string s when s.Length > 10: return "long string";
case null: return "null";
default: return "other";
}
foreach 迭代變數唯讀 — 無法修改集合元素
List list = new() { 1, 2, 3 };
foreach (int item in list)
{
item = 10; // 編譯錯誤:foreach 迭代變數為唯讀
// list[0] = 10; // 這樣可以 (索引器)
}
// 結構體陷阱:foreach 操作的是複本
foreach (var point in points)
{
point.X = 10; // 修改複本,原集合不變!
}
// 必須用 for 迴圈或 List.ForEach
for (int i = 0; i < points.Count; i++) points[i] = new Point(10, points[i].Y);
struct) 是實值型別 — 指派即複製
struct Point { public int X, Y; }
Point p1 = new Point { X = 1, Y = 2 };
Point p2 = p1; // 複製整個結構體
p2.X = 10;
Console.WriteLine(p1.X); // 1 (未受影響)
void Mutate(Point p) { p.X = 99; }
Mutate(p1);
Console.WriteLine(p1.X); // 1 (傳值參數也是複製)
// 加 ref 才能修改原始值
void MutateRef(ref Point p) { p.X = 99; }
MutateRef(ref p1);
Console.WriteLine(p1.X); // 99
string s = "hello";
s += " world"; // 實際上:s = String.Concat(s, " world");
// 原 "hello" 留待 GC 回收
// 大量拼接用 StringBuilder
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.Append(i);
string result = sb.ToString(); // 只分配一次
DateTime 是實值型別 — 不能為 null,用 DateTime?
DateTime dt = DateTime.Now;
// dt = null; // 編譯錯誤
DateTime? nullableDt = null; // 合法
if (nullableDt.HasValue) Console.WriteLine(nullableDt.Value);
// 比較注意:DateTime.MinValue != null
// 資料庫讀取 DBNull.Value 需轉換
DateTime dbDate = reader.IsDBNull(0) ? DateTime.MinValue : reader.GetDateTime(0);
// 或用 GetFieldValue(0) (ADO.NET Core)
class Publisher { public event EventHandler SomethingHappened; }
class Subscriber
{
private Publisher _pub;
public Subscriber(Publisher pub)
{
_pub = pub;
_pub.SomethingHappened += OnHappened; // 訂閱
}
private void OnHappened(object sender, EventArgs e) { }
// 必須實作 IDisposable 或解構子取消訂閱
public void Dispose() => _pub.SomethingHappened -= OnHappened;
~Subscriber() => Dispose(); // 保底
}
// 更好:弱事件模式 (WeakReference) 或 .NET 4.5+ WeakEventManager
async void 僅用於事件處理器 — 其他情況用 async Task
// 正確:事件處理器
private async void Btn_Click(object sender, EventArgs e)
{
await DownloadAsync();
}
// 錯誤:一般方法用 async void → 例外無法捕捉、無法 await
public async void ProcessData() { ... }
// 正確:回傳 Task
public async Task ProcessDataAsync() { ... }
// 呼叫端
await ProcessDataAsync(); // 可捕捉例外、可併發控制
IDisposable 模式與 using 宣告
// 完整 Dispose 模式 (非密封類別)
public class ResourceHolder : IDisposable
{
private SafeHandle _handle; // 非受控資源包裝
private bool _disposed;
public void Dispose() => Dispose(true);
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing) _handle?.Dispose(); // 受控資源
_handle = null; // 非受控資源
_disposed = true;
}
~ResourceHolder() => Dispose(false);
}
// C# 8.0+ using 宣告 (隱含 try/finally)
using var fs = File.OpenRead("test.txt");
using var reader = new StreamReader(fs);
string content = await reader.ReadToEndAsync();
// 區塊結束自動 Dispose
@"" 或 Path.CombineTryParse,物件轉字串用 Convert.ToStringswitch 每個 case 必須結束 (break/return/goto case)foreach 不修改集合,結構體用 forrefStringBuilder 或 string.JoinT? / Nullable<T>IDisposableasync voidSafeHandle + using 宣告這 10 個細節看似瑣碎,卻是 C# 程式碼品質的分水嶺。熟練掌握它們,不僅能避開生產環境的詭異 Bug,更能在 Code Review 中展現專業度。建議新手將此表列印貼在螢幕邊,寫代碼時隨時對照,養成肌肉記憶。