首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 新手必知的 10 個語法細節與陷阱:從 @ 字串到 switch 模式比對

     編輯:關於C語言

前言

C# 語法看似熟悉 (C/C++/Java 風格),卻藏著許多「只差一行、行為大不同」的細節。本文整理 10 個新手最易踩的坑,附上 IL 層級解析與最佳實務,助你寫出更穩健、可維護的代碼。

1. @ 逐字字串字面值


// 一般字串:需雙反斜線跳脫
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}")。

2. 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()。

3. 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";
}

4. 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);

5. 結構體 (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

6. 字串是不可變參考型別 — 每次修改產生新物件


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(); // 只分配一次

7. 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)

8. 事件訂閱記得取消訂閱 — 避免記憶體洩漏


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

9. 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(); // 可捕捉例外、可併發控制

10. 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.Combine
  • [ ] 解析輸入用 TryParse,物件轉字串用 Convert.ToString
  • [ ] switch 每個 case 必須結束 (break/return/goto case)
  • [ ] foreach 不修改集合,結構體用 for
  • [ ] 結構體傳遞預設複製,需修改加 ref
  • [ ] 字串拼接用 StringBuilder 或 string.Join
  • [ ] 可為 null 的實值型別用 T? / Nullable<T>
  • [ ] 事件訂閱成對出現 (+= / -=),實作 IDisposable
  • [ ] 非事件處理器禁用 async void
  • [ ] 非受控資源用 SafeHandle + using 宣告

結語

這 10 個細節看似瑣碎,卻是 C# 程式碼品質的分水嶺。熟練掌握它們,不僅能避開生產環境的詭異 Bug,更能在 Code Review 中展現專業度。建議新手將此表列印貼在螢幕邊,寫代碼時隨時對照,養成肌肉記憶。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved