本工具使用的開源套件

本工具程式碼中捆綁了 1 個開源套件。

C# 速查 — 簡明參考

C# 12(搭配 .NET 8)語法、OOP、LINQ 與最常用標準庫速查手冊,覆蓋約 80% 日常場景。

C#

C# C# 12 (with .NET 8)

.NET (Core / 5+ / 8) · OOP · 泛型 · 函數式 · 併發 · 靜態 · 強類型 · 名義類型

學習路徑

先跑通「Hello World 與構建環境」(dotnet new console)→ 熟悉變量、類型與流程控制 → 用集合與 LINQ 處理數據 → 理解面向對象(class/interface)→ 掌握 async/await 異步編程 → 最後按需查文件、網絡、正則與構建調試。FAQ 節適合回頭避坑。

1.Hello World 與構建環境

從零創建、運行並組織一個 .NET 程序:頂層語句、項目文件、命名空間與命令行參數。

最小程序

C# 9+ 支持頂層語句:Program.cs 裏直接寫執行代碼,無需 class 與 Main 樣板。Console.WriteLine 輸出一行。

1
2
3
// Program.cs
Console.WriteLine("Hello, world!");
// 頂層語句(C# 9+):編譯器自動生成 Main 入口

創建與運行

dotnet CLI 是 .NET 的命令行入口:dotnet new 建項目、dotnet run 編譯並運行、dotnet build 只編譯。

1
2
3
// $ dotnet new console -n app
// $ cd app && dotnet run
// $ dotnet build // 只編譯,生成 bin/Debug

命令行參數

頂層語句中 args 是 string[] 命令行參數,args[0] 起為用户參數。與傳統 Main(string[] args) 等價。

1
2
3
4
5
6
7
// Program.cs
if (args.Length > 0) {
Console.WriteLine($"Hello, {args[0]}!");
} else {
Console.WriteLine("No args");
}
// $ dotnet run -- Nick

退出碼

頂層語句可用 return 指定退出碼,0 表示成功、非 0 表示錯誤類別。Environment.ExitCode 也可讀寫。

1
2
3
// Program.cs
return 0; // 頂層語句直接 return 整數
// 或 Environment.ExitCode = 1;

命名空間

namespace 組織類型避免衝突。文件作用域命名空間(C# 10)用分號聲明,省去大括號縮進。

1
2
3
4
5
6
namespace App.Utils; // 文件作用域命名空間(C# 10)
public class Helper {
public static int Twice(int x) => x * 2;
}
// 訪問:App.Utils.Helper.Twice(3)

using 指令

using 指令導入命名空間,省去全限定名。GlobalUsings.cs 的全局 using 讓整個項目共享導入。

1
2
3
4
5
using System;
using System.Collections.Generic;
// GlobalUsings.cs
// global using System.Linq; // 全局 using(C# 10)
// global using static System.Console;

頂層語句細節

頂層語句編譯為 Program 類的 Main 方法。一個項目只能有一份;需要輔助方法用局部函數。

1
2
3
4
5
Console.WriteLine("start");
int total = Sum(1, 2);
Console.WriteLine(total);
static int Sum(int a, int b) => a + b; // 局部函數

csproj 項目文件

csproj 是項目配置:TargetFramework 目標框架、ImplicitUsings 隱式 using、Nullable 可空上下文。

1
2
3
4
5
6
7
8
9
<!-- app.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

2.變量與常量

類型推斷、const/readonly、可空類型、空合併、nameof 與作用域。

var 類型推斷

var 讓編譯器從初始化器推導類型。局部變量可推斷,字段/屬性不行。初始化器類型明確時優先 var 更簡潔。

1
2
3
4
5
var count = 42; // int
var name = "Nick"; // string
var list = new List<int>(); // List<int>
var value = 3.14; // double
// var 只能用於局部變量,字段必須顯式類型

const 與 readonly

const 是編譯期常量(基本類型),readonly 是運行期只讀字段(可在構造函數賦值)。靜態常量多用 static readonly。

1
2
3
4
public const int MaxRetry = 3; // 編譯期常量
public static readonly DateTime Epoch =
new DateTime(1970, 1, 1); // 運行期只讀
// readonly 字段可在構造函數裏賦值一次

可空類型

Nullable<T>(int? 等)讓值類型可為 null;可空引用類型(? 後綴 + Nullable enable)讓編譯器靜態分析空引用。

1
2
3
4
int? maybe = null; // 可空值類型
int real = maybe ?? 0; // 空合併
string? name = FindName(); // 可空引用類型
if (name is null) return; // 編譯器知道此處後 name 非空

空合併與空條件

?? 在左側為 null 時返回右側;?. 在左側為 null 時短路返回 null。鏈式 ?. 讓深訪問可空安全。

1
2
3
4
5
string? s = null;
var result = s ?? "default"; // "default"
int? len = s?.Length; // null(不拋異常)
var first = s?[0] ?? '-'; // '-'
// ?. 與 ?? 配合:可空鏈式訪問 + 兜底

nameof 表達式

nameof 返回符號名字符串,改名時自動同步。常用於參數校驗、屬性通知、日誌標籤。

1
2
3
4
5
public void SetAge(int age) {
if (age < 0)
throw new ArgumentOutOfRangeException(nameof(age));
}
var prop = nameof(Person.Name); // "Name"

作用域

C# 用大括號定義塊作用域。局部變量可與字段同名但建議避免遮蔽;using 聲明限制資源作用域到塊尾。

1
2
3
4
5
6
int x = 1;
{
int x = 2; // 塊內遮蔽外層局部(可讀性差)
Console.WriteLine(x);
}
Console.WriteLine(x); // 1

init 與屬性初始化

init 訪問器允許在對象初始化器裏賦值、之後只讀。屬性初始化器給默認值,set 訪問器可加校驗。

1
2
3
4
5
6
public class Person {
public string Name { get; init; } = ""; // 僅初始化期可設
public int Age { get; set; }
}
var p = new Person { Name = "Nick", Age = 30 }; // OK
// p.Name = "X"; // 編譯錯誤:init 後只讀

解構

元組與 record 支持解構:用圓括號把元素拆成多個變量。析構方法 Deconstruct 自定義解構邏輯。

1
2
3
4
5
var (sum, count) = (42, 7); // 元組解構
var (name, age) = GetPerson(); // 方法返回元組
record Person(string Name, int Age);
var p = new Person("Nick", 30);
var (n, a) = p; // record 自帶解構

3.數據類型

內置類型、可空、枚舉、結構體、泛型、record、元組與類型轉換。

內置類型

int/long/double/bool 等內置類型映射到 System 類型。var 只是推斷,運行時類型不變。

1
2
3
4
5
6
7
8
int i = 42; // System.Int32
long l = 42L;
double d = 3.14;
float f = 3.14f;
decimal m = 19.99m; // 高精度十進制
bool b = true;
char c = 'A';
string s = "hi";

可空類型

int? 是可空值類型(Nullable<int>),HasValue/Value 訪問。可空引用類型在 Nullable enable 下由編譯器靜態檢查。

1
2
3
4
5
int? x = null;
if (x.HasValue) { int v = x.Value; }
int y = x ?? 0; // 空合併
string? name = null; // 可空引用類型
// Nullable enable 時對 name 直接訪問會警告

枚舉

enum 定義命名整型常量,底層默認 int。Flags 特性讓位組合可用 HasFlag 或位運算判斷。

1
2
3
4
5
6
enum Color { Red, Green, Blue } // 默認 0,1,2
[Flags]
enum Perm { Read = 1, Write = 2, Exec = 4 }
var p = Perm.Read | Perm.Write;
if ((p & Perm.Write) != 0) { } // 位判斷
var c = (Color)1; // Green

結構體

struct 是值類型:賦值拷貝整塊、棧上分配、不能為 null(可空時裝箱)。小且不變的數據用 struct。

1
2
3
4
5
6
struct Point {
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
}
Point a = new(1, 2);
Point b = a; // 值拷貝,b 改不影響 a

類(引用類型)

class 是引用類型:賦值共享同一對象、GC 管理內存、可為 null。可變狀態多用 class。

1
2
3
4
5
6
class Counter {
public int Value { get; set; }
}
Counter a = new() { Value = 1 };
Counter b = a; // 引用拷貝,共享同一對象
b.Value = 99; // a.Value 也是 99

泛型

泛型把類型參數化,編譯期生成強類型代碼、無運行時裝箱。T 是類型參數,where 子句加約束。

1
2
3
4
5
public class Box<T> {
public T Value { get; set; }
}
var b = new Box<int> { Value = 42 };
// 約束:where T : class, new()

record 類型

record(C# 9)是值語義的引用類型:自帶值相等、ToString、解構、with 表達式。適合 DTO 與不可變數據。

1
2
3
4
5
record Person(string Name, int Age);
var p1 = new Person("Nick", 30);
var p2 = p1 with { Age = 31 }; // 非破壞性修改
bool eq = p1 == p2; // false(按值比較)
var (name, age) = p1; // 解構

元組

元組把多個值打包,支持命名元素。返回多個值、快速聚合數據時最方便,臨時用元組、長期用 record。

1
2
3
4
var t = (sum: 42, count: 7); // 命名元組
Console.WriteLine(t.sum);
(int, int) swap((int a, int b) t) => (t.b, t.a);
var (a, b) = (1, 2);

類型轉換

is 安全類型檢查、as 安全轉換(失敗返 null)、(T) 強制轉換(失敗拋異常)、Convert/Parse 顯式解析。

1
2
3
4
5
6
object o = "hello";
if (o is string s) { } // 模式匹配 + 轉換
var str = o as string; // null 或 string
// var bad = (int)o; // 拋 InvalidCastException
int n = int.Parse("42"); // 顯式解析
bool ok = int.TryParse("42", out int v); // 安全解析

模式匹配

is/switch 用模式匹配:類型模式、屬性模式、位置模式。替代大量 if + cast,代碼更表達意圖。

1
2
3
4
5
6
7
string Describe(object o) => o switch {
int n when n > 0 => "positive",
int n => "integer",
string s when s.Length > 3 => "long string",
null => "null",
_ => "other", // 兜底
};

4.引用與數組

值類型與引用類型、數組、Span、索引範圍、ref/in/out 與 unsafe 指針。

值類型 vs 引用類型

值類型(struct/枚舉/基本類型)賦值即拷貝;引用類型(class/接口)賦值共享對象。這是 C# 最重要的心智模型。

1
2
3
4
5
6
7
8
// struct 值拷貝:b 是獨立副本
Point a = new(1, 2);
Point b = a;
b.X = 99; // a.X 仍是 1
// class 引用共享
var c1 = new Counter();
var c2 = c1;
c2.Value = 5; // c1.Value 也是 5

數組

數組是定長引用類型,[] 下標訪問。多維與交錯數組不同:[,] 矩形、[][] 鋸齒。數組用 Length 而非 Count。

1
2
3
4
5
6
int[] arr = { 1, 2, 3 };
int first = arr[0];
arr[2] = 99;
int len = arr.Length; // 3
int[,] rect = new int[2, 3]; // 二維矩形
int[][] jag = new int[2][]; // 交錯數組

索引與範圍

^ 從尾部索引(^1 最後一個)、.. 表示範圍(1..^1 去掉首尾)。切數組/List 子集的現代寫法。

1
2
3
4
5
int[] arr = { 1, 2, 3, 4, 5 };
int last = arr[^1]; // 5
int secondLast = arr[^2]; // 4
int[] mid = arr[1..^1]; // { 2, 3, 4 }
int[] tail = arr[2..]; // { 3, 4, 5 }

Span 與內存

Span<T> 是任意連續內存的只讀視圖,無分配、可切片,是高性能數據處理的核心。棧或堆內存都能指向。

1
2
3
4
5
int[] arr = { 1, 2, 3, 4 };
Span<int> s = arr;
Span<int> part = s[1..3]; // 切片,無拷貝
part[0] = 99; // 修改原數組
// 也支持棧內存:Span<int> st = stackalloc int[4];

ref / in / out 參數

ref 按引用傳遞可讀寫、in 按引用傳遞只讀、out 用於返回值(調用前可不初始化)。按引用避免值類型拷貝。

1
2
3
4
5
6
7
void Increment(ref int x) => x++;
void Init(out int x) => x = 42;
int ReadOnly(in int x) => x;
int v = 1;
Increment(ref v); // v = 2
Init(out v); // v = 42
ReadOnly(in v); // 只讀引用

unsafe 指針

unsafe 上下文可用真指針(int* 等),需在 csproj 開 AllowUnsafeBlocks。僅與原生交互時用,普通代碼避免。

1
2
3
4
5
6
unsafe {
int x = 42;
int* p = &x; // 取地址
Console.WriteLine(*p); // 解引用
}
// csproj: <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

Memory 與緩衝區

Memory<T> 是 Span 的堆安全版本,可存字段、跨 async。ReadOnlyMemory 只讀視圖。用於異步緩衝數據處理。

1
2
3
4
byte[] data = GetBytes();
Memory<byte> mem = data; // 可跨 async 保存
ReadOnlyMemory<byte> rom = mem; // 只讀視圖
// Span 不能存字段/跨 await,Memory 可以

拷貝語義

值類型默認按值傳遞(大 struct 有拷貝開銷);對象(引用)按引用傳遞。需要原地修改對象則不用 ref。

1
2
3
4
5
struct Big { public long A, B, C, D; }
void PassByValue(Big b) { } // 拷貝整個 struct
void PassByRef(ref Big b) { } // 按引用,零拷貝
var x = new Big();
PassByRef(ref x); // 原對象被修改

5.流程控制

if/else、for/foreach/while、switch 表達式、break/continue 與 throw 表達式。

if / else

if/else 按條件分支。C# 用 == 比較、&& 與 || 短路。單語句可省大括號但建議保留。

1
2
3
4
5
6
7
if (score >= 90) {
grade = "A";
} else if (score >= 60) {
grade = "B";
} else {
grade = "F";
}

for 循環

經典 for:初始化、條件、步進。需要下標或索引時用;多數情況 foreach 更安全。

1
2
3
4
for (int i = 0; i < 10; i++) {
Console.WriteLine(i); // 0..9
}
for (int i = arr.Length - 1; i >= 0; i--) { } // 逆序

foreach 遍歷

foreach 遍歷集合無需下標,編譯期安全。配合 var 與 LINQ 鏈式處理數據最常用。

1
2
3
4
5
foreach (var item in items) {
Console.WriteLine(item);
}
// 需要下標時用 for 或 Enumerable.Select
foreach (var (i, item) in items.Select((x, i) => (x, i))) { }

while / do-while

while 先判斷後執行;do-while 至少執行一次。讀取流直到結束等循環次數未知的場景用。

1
2
3
4
5
6
int i = 0;
while (i < 5) { i++; } // 先判斷
string? line;
do {
line = Console.ReadLine();
} while (line != null); // 至少一次

switch 表達式

switch 表達式(C# 8)用 => 返回值,替代長 if-else 鏈。類型模式與 when 守衞配合強大。

1
2
3
4
5
6
7
8
9
10
11
string Describe(int n) => n switch {
0 => "zero",
1 => "one",
_ => "other", // 兜底
};
// 類型模式:switch 對象按類型分發
string Type(object o) => o switch {
int => "int",
string => "string",
_ => "other",
};

break 與 continue

continue 跳本輪進入下一輪;break 退出循環;return 退出方法。多層循環用 goto 跳出。

1
2
3
4
5
6
7
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue; // 跳過偶數
if (i > 7) break; // 退出
}
// 跳出雙層循環
goto Exit;
Exit: ;

三元運算符

cond ? a : b 一行表達 if/else 賦值。分支表達式類型必須兼容(可轉換到共同類型)。

1
2
var grade = score >= 60 ? "pass" : "fail";
var label = score switch { >= 90 => "A", _ => "other" };

throw 表達式

throw 可作為表達式用在 ?? 與 ?: 右側、方法體單行。拋 ArgumentNullException 校驗參數簡潔。

1
2
3
string s = name ?? throw new ArgumentNullException(nameof(name));
int v = dict.TryGetValue(k, out var val) ? val :
throw new KeyNotFoundException(k);

6.函數與方法

方法簽名、參數傳遞、重載、局部函數、Lambda 與表達式體成員。

方法定義

方法 = 返回類型 + 名稱 + 參數表 + 主體。返回 void 表示無返回值。訪問修飾符控制可見性。

1
2
3
4
5
6
7
public int Add(int a, int b) {
return a + b;
}
public void Say(string msg) {
Console.WriteLine(msg); // void 無返回
}
private int twice = 0; // 字段

參數傳遞

默認按值傳遞:方法內修改不影響調用方變量。對象傳的是引用副本,改成員會影響原對象。

1
2
3
4
5
6
void Set(int x) { x = 99; } // 按值:外部不變
void Mutate(List<int> l) { l.Add(1); } // 引用:外部可見
var n = 1;
Set(n); // n 仍是 1
var list = new List<int>();
Mutate(list); // list 現在是 [1]

可選參數與命名參數

可選參數給默認值(必須放參數表末尾);命名參數按名字傳參,可跳過中間可選參數。

1
2
3
4
5
6
void Greet(string name, string prefix = "Hello", int times = 1) {
Console.WriteLine($"{prefix}, {name}");
}
Greet("Nick"); // 用默認
Greet("Nick", "Hi");
Greet("Nick", times: 3); // 命名參數跳過 prefix

ref / out / in

ref 引用傳遞可讀可寫;out 只用於輸出(調用前可不初始化);in 引用傳遞只讀(避免大值類型拷貝)。

1
2
3
4
5
6
7
void Ref(ref int x) => x++;
void Out(out int x) => x = 42;
void In(in int x) { /* 只讀 */ }
int v = 1;
Ref(ref v); // v = 2
Out(out v); // v = 42
In(in v); // 零拷貝只讀

方法重載

重載 = 同名方法不同參數列表(數量/類型)。編譯器按實參選最匹配版本。返回類型不能參與重載區分。

1
2
3
4
int Parse(string s) => int.Parse(s);
int Parse(int x) => x; // 重載:參數類型不同
long Parse(string s, int radix) => // 重載:參數數量不同
Convert.ToInt64(s, radix);

表達式體成員

方法/屬性/構造函數為單個表達式時可用 => 簡寫。lambda 類似,但表達式體成員是真正的成員。

1
2
3
4
5
public int Square(int x) => x * x;
public string Name => "Nick"; // 只讀屬性
public void Reset() => Count = 0;
// 構造函數也可以:
class Box { public Box(string s) => Label = s; string Label { get; } }

局部函數

方法內定義的方法可訪問外層變量(閉包),常用於遞歸輔助或迭代器內部邏輯。

1
2
3
4
5
int Factorial(int n) {
int Helper(int x) => x <= 1 ? 1 : x * Helper(x - 1);
return Helper(n);
}
// Helper 只能在此方法內使用

Lambda 表達式

匿名函數:參數 => 表達式。配合委託/Func/Action 使用,是 LINQ 的核心語法。

1
2
3
4
5
6
7
8
Func<int, int> square = x => x * x;
Func<int, int, int> add = (a, b) => a + b;
Action<string> print = msg => Console.WriteLine(msg);
// 多行體:
Func<int, int> twice = (x) => {
int r = x * 2;
return r;
};

委託

委託是方法類型:聲明簽名,用 += 訂閲多個方法,調用委託會依次觸發。事件基於委託。

1
2
3
4
5
delegate void Notify(string msg);
void Log(string m) => Console.WriteLine(m);
Notify n = Log; // 委託指向方法
n += m => Console.WriteLine("!" + m);
n("hi"); // 依次調用所有訂閲

7.字符串

字符串字面量、插值、常用方法、可變 StringBuilder 與格式化。

字符串字面量

雙引號普通字符串、@ 原義字符串(轉義符不處理)、$ 插值、$$ 複合字面量(C# 11)。

1
2
3
string s = "Line\n\tIndent"; // 轉義
string raw = @"C:\Program Files\"; // 原義:\ 不轉義
string path = "C:\\Program Files\\"; // 等價普通寫法

字符串插值

$ 前綴把 {} 表達式結果內聯進字符串;可帶格式與對齊。比 + 拼接更可讀。

1
2
3
4
5
int age = 30;
string name = "Nick";
var msg = $"{name} is {age} years old";
var pad = $"{name,10}"; // 右對齊 10 寬
var money = $"{age:C}"; // 貨幣格式

拼接與比較

+ 拼接、string.Concat 批量、string.Join 帶分隔符。字符串相等用 ==(值比較)而非 == 引號。

1
2
3
4
string a = "foo" + "bar"; // "foobar"
string all = string.Join(", ", names); // "a, b, c"
bool eq = a == "foobar"; // true(值比較)
int cmp = string.Compare("a", "b"); // 字典序比較

常用方法

Length、Substring、Contains、StartsWith、IndexOf、Replace、Trim、Split、ToUpper/ToLower 覆蓋絕大多數字符串處理。

1
2
3
4
5
6
string s = " Hello, World ";
int len = s.Length; // 14
var sub = s.Substring(7, 5); // "World"
bool has = s.Contains("World"); // true
var rep = s.Replace("World", "C#");
var words = s.Trim().Split(","); // ["Hello", " World"]

StringBuilder

大量拼接用 StringBuilder 避免反覆建字符串(字符串不可變,+ 每次生成新對象)。循環內拼接尤其明顯。

1
2
3
4
5
var sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.Append(i).Append(",");
}
string result = sb.ToString(); // 一次性轉字符串

字符 char

char 是 UTF-16 單字符。char.IsDigit/IsLetter/IsWhiteSpace 判斷類別。string 不可變、char 可遍歷。

1
2
3
4
5
6
char c = 'A';
bool digit = char.IsDigit(c); // false
bool upper = char.IsUpper(c); // true
foreach (char ch in "Hi") {
Console.WriteLine(ch); // 'H', 'i'
}

格式化

string.Format / $ 插值用格式説明符:D 整數、F 小數、C 貨幣、P 百分比、X 十六進制。

1
2
3
4
5
double d = 1234.567;
Console.WriteLine($"{d:F2}"); // 1234.57
Console.WriteLine($"{d:C}"); // ¥1,234.57(依區域)
Console.WriteLine($"{42:D5}"); // 00042
Console.WriteLine($"{255:X}"); // FF

解析字符串

Parse 轉換字符串到數字(失敗拋異常),TryParse 安全(返回 bool + out 結果)。TryParse 優先。

1
2
3
4
int n = int.Parse("42"); // 可能拋異常
bool ok = int.TryParse("42", out int v); // 安全
if (ok) Console.WriteLine(v);
// 也可:int.Parse("ff", NumberStyles.HexNumber)

8.集合與 LINQ

List/Dictionary/HashSet 等常用集合、IEnumerable 與 LINQ 鏈式查詢。

List 動態數組

List<T> 可變長數組,Add/Insert/Remove/IndexOf,按下標 O(1) 訪問。遍歷用 foreach。

1
2
3
4
5
6
var list = new List<int> { 1, 2, 3 };
list.Add(4);
list.Insert(0, 0); // [0,1,2,3,4]
list.Remove(2);
int count = list.Count; // Length 換 Count
bool any = list.Contains(4);

Dictionary 字典

Dictionary<K,V> 鍵值映射,按鍵 O(1) 查詢。TryGetValue 安全取值,遍歷 KeyValuePair。

1
2
3
4
5
6
7
8
var dict = new Dictionary<string, int>();
dict["Nick"] = 30;
if (dict.TryGetValue("Nick", out int age)) {
Console.WriteLine(age);
}
foreach (var kv in dict) {
Console.WriteLine($"{kv.Key}: {kv.Value}");
}

HashSet 集合

HashSet<T> 無重複、O(1) 判斷包含。UnionWith/IntersectWith/ExceptWith 做集合運算去重合並。

1
2
3
4
5
6
var set = new HashSet<int> { 1, 2, 3 };
set.Add(3); // 已存在,無效果
bool has = set.Contains(2);
set.UnionWith(new[] { 3, 4 }); // {1,2,3,4}
var common = new HashSet<int> { 2 };
common.IntersectWith(set); // {2}

Queue 與 Stack

Queue<T> 先進先出(Enqueue/Dequeue),Stack<T> 後進先出(Push/Pop)。處理任務隊列、撤銷棧。

1
2
3
4
5
6
var q = new Queue<int>();
q.Enqueue(1); q.Enqueue(2);
int next = q.Dequeue(); // 1
var s = new Stack<int>();
s.Push(1); s.Push(2);
int top = s.Pop(); // 2

IEnumerable 與惰性

IEnumerable<T> 是隻讀序列接口,LINQ 基於它惰性求值:遍歷時才算。數組/List/字典都實現它。

1
2
3
4
IEnumerable<int> nums = GetNums(); // 惰性:還沒算
int sum = nums.Where(n => n > 0)
.Sum(); // 遍歷時才求值
// ToList()/ToArray() 物化,立即執行全部

LINQ 過濾與映射

Where 過濾、Select 投影(映射)、OrderBy 排序、Distinct 去重。方法鏈可讀且惰性。

1
2
3
4
5
var adults = people
.Where(p => p.Age >= 18) // 過濾
.OrderByDescending(p => p.Age) // 排序
.Select(p => p.Name) // 投影
.ToList(); // 物化

LINQ 聚合

Count/Sum/Average/Min/Max 聚合,Any/All 判斷,First/Single 取元素(無匹配拋異常,FirstOrDefault 返默認)。

1
2
3
4
5
6
7
int[] nums = { 1, 2, 3, 4 };
int sum = nums.Sum(); // 10
double avg = nums.Average(); // 2.5
bool anyEven = nums.Any(n => n % 2 == 0); // true
bool allPos = nums.All(n => n > 0); // true
int first = nums.First(n => n > 2); // 3
int? maybe = nums.FirstOrDefault(n => n > 9); // null

GroupBy 分組

GroupBy 按鍵分組,產生 IGrouping 序列,常用於統計(計數、求和按類別)。

1
2
3
4
5
6
7
8
var stats = orders
.GroupBy(o => o.Region)
.Select(g => new {
Region = g.Key,
Total = g.Sum(o => o.Amount),
Count = g.Count(),
})
.ToList();

LINQ 查詢語法

from/where/orderby/select 查詢語法是方法鏈的聲明式寫法,編譯後等價。可讀性偏好問題。

1
2
3
4
5
6
7
8
var adults = from p in people
where p.Age >= 18
orderby p.Age descending
select p.Name;
// 等價方法鏈:
var same = people.Where(p => p.Age >= 18)
.OrderByDescending(p => p.Age)
.Select(p => p.Name);

9.內存與資源管理

GC 自動回收、IDisposable 與 using 釋放非託管資源、弱引用與對象池。

GC 垃圾回收

C# 內存由 GC 自動管理:堆上對象不再引用時被回收。代際回收(0/1/2)優化性能。

1
2
3
var obj = new object(); // 堆分配
// 無引用後 GC 自動回收,無需手動 free
GC.Collect(); // 強制回收(一般不要手動調用)

IDisposable 接口

持有非託管資源(文件/網絡/數據庫連接)的類實現 IDisposable,在 Dispose 裏釋放資源。

1
2
3
4
5
6
7
class FileWriter : IDisposable {
private StreamWriter? _sw;
public void Dispose() {
_sw?.Dispose(); // 釋放非託管資源
}
}
using (var w = new FileWriter()) { } // 自動 Dispose

using 語句與聲明

using 保證作用域結束時 Dispose(finally 語義)。using 聲明(C# 8)讓資源隨塊結束自動釋放。

1
2
3
4
5
using var reader = new StreamReader("file.txt");
string line = reader.ReadLine();
// 作用域結束自動 Dispose,無需顯式
// 等價傳統寫法:
using (var r = new StreamReader("file.txt")) { }

終結器

析構函數 ~Class() 是終結器:GC 回收前調用,無法保證時機。僅用於釋放非託管資源,正常應走 Dispose 模式。

1
2
3
4
5
6
class Resource {
~Resource() {
// GC 前調用,時機不確定——不要依賴
// 應當實現 IDisposable 並顯式釋放
}
}

弱引用

WeakReference 不阻止 GC 回收目標,用於緩存(字典緩存重對象、允許被回收)。Target 可能隨時變 null。

1
2
3
4
5
var weak = new WeakReference(new byte[1024]);
if (weak.IsAlive) {
var data = (byte[])weak.Target!; // 可能已被回收
}
// GC 後可變為 IsAlive == false

對象池與 ArrayPool

頻繁分配大數組用 ArrayPool 複用緩衝減少 GC 壓力。ArrayPool<T>.Shared.Rent/Return 成對使用。

1
2
3
4
5
6
7
byte[] buf = ArrayPool<byte>.Shared.Rent(1024);
try {
// 使用 buf
} finally {
ArrayPool<byte>.Shared.Return(buf);
}
// Rent 的數組可能比請求大,用後必須 Return

stackalloc 棧分配

stackalloc 在棧上分配內存,速度快且不觸發 GC,適合小型臨時緩衝,棧空間有限需謹慎使用。

1
2
3
4
5
6
int length = 4;
// stackalloc 在棧上分配,速度快且不觸發 GC
Span<int> buffer = stackalloc int[length];
buffer[0] = 42;
foreach (var n in buffer) { Console.WriteLine(n); }
// 棧空間有限,只適合小型臨時緩衝

GC 內存壓力

分配大量原生內存時用 GC.AddMemoryPressure 告知 GC,讓回收器及時回收託管對象,避免內存佔用失控。

1
2
3
4
5
6
7
8
9
10
var size = 64 * 1024 * 1024; // 64 MB
IntPtr buffer = System.Runtime.InteropServices.Marshal.AllocHGlobal(size);
GC.AddMemoryPressure(size); // 告知 GC 原生內存壓力
try {
// 使用原生緩衝區(示例:寫入一個字節)
System.Runtime.InteropServices.Marshal.WriteByte(buffer, 0, 1);
} finally {
System.Runtime.InteropServices.Marshal.FreeHGlobal(buffer);
GC.RemoveMemoryPressure(size); // 釋放後移除壓力
}

10.面向對象

類、屬性、構造、繼承、多態、接口、抽象類與訪問修飾符。

類與對象

class 定義數據類型(引用類型)。字段存狀態、屬性控制訪問、方法定義行為、構造函數初始化。

1
2
3
4
5
6
7
8
9
public class Person {
public string Name { get; set; } = "";
public int Age { get; set; }
public Person(string name, int age) {
Name = name; Age = age;
}
public string Greet() => $"Hi, {Name}";
}
var p = new Person("Nick", 30);

屬性

屬性是字段的安全訪問器:get 讀、set 寫,可加訪問修飾符與校驗邏輯。編譯器生成後備字段。

1
2
3
4
5
6
7
8
9
10
private int _age;
public int Age {
get => _age;
set {
if (value < 0) throw new ArgumentOutOfRangeException();
_age = value;
}
}
// 自動屬性:
public string Name { get; set; } = ""

繼承

C# 單繼承:class 用 : 繼承一個基類。派生類 is-a 基類。私有成員不繼承,protected 可訪問。

1
2
3
4
5
6
7
8
9
public class Animal {
public string Name { get; set; } = "";
public virtual void Speak() => Console.WriteLine("...");
}
public class Dog : Animal {
public override void Speak() => Console.WriteLine("Woof");
}
Animal a = new Dog();
a.Speak(); // "Woof"(多態)

多態

virtual + override 實現多態:基類引用調用的方法按實際類型分派。方法必須 virtual 才能被重寫。

1
2
3
4
5
6
7
8
9
public class Shape {
public virtual double Area() => 0;
}
public class Circle : Shape {
public double R { get; set; }
public override double Area() => Math.PI * R * R;
}
Shape s = new Circle { R = 2 };
double area = s.Area(); // 12.57(按實際類型)

抽象類

abstract 類不能實例化,可含 abstract 方法(子類必須實現)。抽象方法無主體。用於模板基類。

1
2
3
4
5
6
7
8
9
public abstract class Shape {
public abstract double Area(); // 無實現
public void Describe() =>
Console.WriteLine($"Area: {Area()}");
}
public class Square : Shape {
public double Side { get; set; }
public override double Area() => Side * Side;
}

接口

interface 定義契約:成員無實現,實現類全部實現。C# 支持多接口實現(多繼承替代)。

1
2
3
4
5
6
7
8
public interface ILogger {
void Log(string msg);
}
public class ConsoleLogger : ILogger {
public void Log(string msg) => Console.WriteLine(msg);
}
ILogger logger = new ConsoleLogger();
logger.Log("hi"); // 面向接口編程

訪問修飾符

public 公開、private 私有、protected 派生可見、internal 程序集內可見。默認類 private、成員 private。

1
2
3
4
5
6
public class Account {
public decimal Balance { get; private set; } // 外部讀、內部寫
private int _transactions; // 僅本類
protected void Audit() { } // 僅派生類
}
// internal:本程序集可見(默認 for 頂級類)

sealed 與 object 方法

sealed 類不可被繼承、override 方法不可再被重寫。所有類隱式繼承 object(ToString/Equals/GetHashCode)。

1
2
3
4
5
6
7
8
public sealed class Config { } // 禁止繼承
public class Base {
public virtual void Run() { }
}
public class Child : Base {
public override void Run() { } // 可重寫
}
// 若 Run 在 Child 標 sealed override,則孫類不能重寫

靜態類與擴展方法

static class 只能含靜態成員、不能實例化。擴展方法:靜態類裏的靜態方法,this 參數讓實例可調用。

1
2
3
4
5
6
public static class StringExt {
public static bool IsEmpty(this string s) =>
string.IsNullOrEmpty(s);
}
string s = "";
bool empty = s.IsEmpty(); // 實例語法調用擴展方法

11.異常處理

try/catch/finally、異常類型、自定義異常、異常的代價與最佳實踐。

try / catch / finally

try 放可能出錯的代碼;catch 捕獲處理;finally 無論成敗都執行(清理資源)。異常向上傳播。

1
2
3
4
5
6
7
try {
int.Parse("abc");
} catch (FormatException ex) {
Console.WriteLine(ex.Message); // 處理錯誤
} finally {
Console.WriteLine("cleanup"); // 總會執行
}

多 catch 與異常過濾

多個 catch 按類型匹配,特定異常在前。when 過濾條件。catch 不聲明變量可忽略異常對象。

1
2
3
4
5
6
try {
Process();
} catch (FileNotFoundException ex) when (ex.FileName == "cfg")
{ /* 過濾:僅當文件名匹配 */ }
catch (IOException ex) { /* 更泛的異常 */ }
catch { /* 捕獲一切 */ }

拋出異常

throw new 拋異常;throw; 不帶參數原樣重拋(保留堆棧)。new 而 throw 在 catch 裏會重置堆棧。

1
2
3
4
5
6
throw new ArgumentException("invalid value", nameof(value));
try { ... }
catch (Exception ex) {
// throw; // 保留堆棧
// throw ex; // 重置堆棧(丟信息,避免)
}

自定義異常

自定義異常繼承 Exception(慣例加 Exception 後綴),提供構造函數,保留內部異常 InnerException。

1
2
3
4
5
6
7
public class ConfigException : Exception {
public ConfigException() { }
public ConfigException(string message) : base(message) { }
public ConfigException(string message, Exception inner)
: base(message, inner) { }
}
throw new ConfigException("bad config");

finally 清理

finally 保證資源釋放/狀態恢復,無論 try 是否異常。return 也會先執行 finally。using 是它的語法糖。

1
2
3
4
5
6
7
try {
lockObj.Enter();
DoWork();
} finally {
lockObj.Exit(); // 無論成敗都解鎖
}
// return 在 finally 之後返回,finally 一定執行

異常的代價

異常捕獲開銷高,不要用異常做流程控制。可預判的錯誤用返回碼/TryXxx/Result 模式處理。

1
2
3
4
5
6
// 慢:用異常做流程控制
bool ok1;
try { int.Parse(s); ok1 = true; }
catch { ok1 = false; }
// 快:TryParse 直接返回結果
bool ok2 = int.TryParse(s, out _);

全局異常處理

頂層用 try/catch 捕獲未處理異常,記錄日誌。ASP.NET 用中間件(UseExceptionHandler)統一處理。

1
2
3
4
5
6
7
try {
await RunAsync();
} catch (Exception ex) {
Log(ex); // 記錄
Environment.ExitCode = 1; // 標記失敗
}
// ASP.NET Core:app.UseExceptionHandler(...)

InnerException 鏈

捕獲異常後可拋新異常並傳入原異常作為 InnerException,保留完整錯誤鏈,日誌排查更有價值。

1
2
3
4
5
6
7
try {
LoadConfig();
} catch (FileNotFoundException ex) {
// 包裝異常並保留原始原因
throw new InvalidOperationException("配置缺失", ex);
}
// 排查:ex.InnerException 可逐層回溯根因

12.文件與 IO

File/Directory/Path 靜態工具、Stream 讀寫、異步 IO 與二進制處理。

讀寫文本文件

File.ReadAllText/WriteAllText 一次讀寫小文件;ReadAllLines 逐行數組。大文件用 StreamReader。

1
2
3
4
5
string content = File.ReadAllText("in.txt");
File.WriteAllText("out.txt", content);
string[] lines = File.ReadAllLines("in.txt");
File.WriteAllLines("out.txt", lines);
// 編碼:File.ReadAllText(path, Encoding.UTF8)

StreamReader 逐行

大文件逐行流式讀取不佔內存。StreamReader.ReadLine 循環直到 null(文件尾)。

1
2
3
4
5
6
using var reader = new StreamReader("big.log");
string? line;
while ((line = reader.ReadLine()) != null) {
Process(line);
}
// using 確保讀完自動釋放

目錄操作

Directory 創建/刪除/枚舉目錄,Directory.GetFiles/EnumerateFiles 找文件。遞歸枚舉用 SearchOption。

1
2
3
4
5
6
Directory.CreateDirectory("data");
if (Directory.Exists("data")) { }
string[] files = Directory.GetFiles("data", "*.txt");
// 遞歸:
var all = Directory.EnumerateFiles("data", "*",
SearchOption.AllDirectories);

Path 路徑處理

Path 跨平台拼接/解析路徑:Combine、GetExtension、GetFileName、ChangeExtension。不要手拼路徑分隔符。

1
2
3
4
string dir = Path.Combine("data", "files");
string ext = Path.GetExtension("a.txt"); // ".txt"
string name = Path.GetFileName("/a/b.txt"); // "b.txt"
string changed = Path.ChangeExtension("a.txt", ".md");

異步 IO

async/await IO 不阻塞線程:ReadAllTextAsync/WriteAllTextAsync/Stream 方法。UI/服務器併發場景必須用。

1
2
3
4
5
6
string text = await File.ReadAllTextAsync("in.txt");
await File.WriteAllTextAsync("out.txt", text);
// async Main / async 方法內可用
using var reader = new StreamReader("big.log");
string? line;
while ((line = await reader.ReadLineAsync()) != null) { }

二進制讀寫

BinaryWriter/BinaryReader 按類型讀寫;MemoryStream 內存流。網絡與文件二進制協議常用。

1
2
3
4
5
6
7
using var ms = new MemoryStream();
using var w = new BinaryWriter(ms);
w.Write(42); w.Write("data");
ms.Position = 0;
using var r = new BinaryReader(ms);
int n = r.ReadInt32();
string s = r.ReadString();

控制台 IO

Console.ReadLine 讀一行、ReadKey 讀按鍵、WriteLine 輸出。重定向流 Console.In/Out 可測試。

1
2
3
4
5
Console.Write("Name: ");
string? name = Console.ReadLine();
var key = Console.ReadKey(); // 讀單個按鍵
Console.WriteLine($"Hello {name} ({key.KeyChar})");
// 輸入重定向:dotnet run < input.txt

文件信息

FileInfo/DirectoryInfo 提供文件元數據與方法;File.Exists 檢查存在性;GetCreationTime 時間戳。

1
2
3
4
5
6
7
var fi = new FileInfo("a.txt");
if (fi.Exists) {
long bytes = fi.Length;
DateTime created = fi.CreationTime;
fi.CopyTo("b.txt");
}
bool ok = File.Exists("a.txt");

13.常見陷阱(FAQ)

C# 開發者最常踩的坑:值類型 vs 引用、字符串相等、LINQ 惰性、async/await 死鎖等。

字符串 == 與 Equal

C# 的 string == 按值比較(不同於 Java)。但 == 用序比較,Equals 可指定 StringComparison 做忽略大小寫等。

1
2
3
4
5
// BAD:依賴默認序比較,中文/大小寫可能不符預期
bool bad = a == "nick";
// GOOD:顯式指定比較規則
bool good = a.Equals("nick", StringComparison.OrdinalIgnoreCase);

傳值 vs 傳引用

方法參數默認按值傳:對象傳的是引用副本,改成員會影響原對象;值類型拷貝。忘記這點是常見 bug。

1
2
3
4
5
6
7
8
9
// BAD:以為 List 傳值不會被改
void Bad(List<int> l) => l.Add(1);
var list = new List<int>();
Bad(list); // list 其實被改了
// GOOD:顯式意識到引用傳遞,或用 ref 表達意圖
void Good(ref List<int> l) => l.Add(1);
var list2 = new List<int>();
Good(ref list2);

LINQ 惰性求值

LINQ 查詢是惰性的:不枚舉不算。把 IQueryable/IEnumerable 傳出去後再枚舉可能有副作用或已過期數據。

1
2
3
4
5
6
// BAD:LINQ 惰性,list 再改會改變查詢結果
var q = list.Where(n => n > 0);
list.Add(5); // q 結果變化
// GOOD:物化快照
var snapshot = list.Where(n => n > 0).ToList();

async void 與死鎖

事件處理器可用 async void,普通方法必須返回 Task。sync 上下文(UI/WinForms)裏 .Result/.Wait() 會死鎖。

1
2
3
4
5
6
// BAD:async void 方法,異常無法捕獲,測試脆弱
async void Bad() { await Task.Delay(10); }
// GOOD:返回 Task,await 傳播異常
async Task Good() { await Task.Delay(10); }
// UI 中別用 .Result 阻塞等待異步方法

異常吞掉與重拋

catch 後不處理就吞掉;throw ex 重置堆棧丟失原異常鏈。日誌必須保留 InnerException。

1
2
3
4
5
6
7
8
// BAD:吞掉異常,調試找不到問題
catch (Exception ex) { }
// GOOD:記錄或原樣重拋
catch (Exception ex) {
_logger.LogError(ex, "failed");
throw; // 保留堆棧
}

資源未釋放

Stream/HttpClient/數據庫連接不釋放會泄漏資源。IDisposable 一律用 using 或用完 Dispose。

1
2
3
4
5
6
7
// BAD:Stream 未釋放
var sr = new StreamReader("a.txt");
string t = sr.ReadToEnd();
// GOOD:using 保證釋放
using var good = new StreamReader("a.txt");
string t2 = good.ReadToEnd();

浮點比較

float/double 二進制表示不精確,直接 == 比較會出意外。用 epsilon 容差或 decimal 精確運算。

1
2
3
4
5
6
// BAD:浮點精確相等不可靠
bool bad = (0.1 + 0.2) == 0.3; // false
// GOOD:容差比較
bool good = Math.Abs((0.1 + 0.2) - 0.3) < 1e-9;
// 金額用 decimal:0.1m + 0.2m == 0.3m 為 true

遍歷時修改集合

foreach 裏 Add/Remove 集合拋 InvalidOperationException。收集要刪的元素,遍歷後再刪除。

1
2
3
4
5
6
7
8
// BAD:foreach 中 Remove 拋異常
foreach (var x in list) {
if (x < 0) list.Remove(x);
}
// GOOD:收集後統一刪除
var toRemove = list.Where(x => x < 0).ToList();
foreach (var x in toRemove) list.Remove(x);

可空與空引用

Nullable enable 後編譯器幫助發現空引用。避免直接返回 null 歧義,用 ?? / ?. 提供兜底。

1
2
3
4
5
6
7
// BAD:Nullable 關閉 + 直接解引用
string s = GetMaybe();
Console.WriteLine(s.Length); // 可能 NullReference
// GOOD:可空註解 + 空合併
string? maybe = GetMaybe();
Console.WriteLine((maybe ?? "").Length);

本地時間 vs UTC

存儲/傳輸時間用 UTC(DateTimeKind.Utc),展示時轉本地。混用 Kind 會靜默算錯時間。

1
2
3
4
5
6
7
// BAD:默認 Kind 未指定,來回轉換出錯
var now = DateTime.Now;
// GOOD:明確 UTC 存儲
var utc = DateTime.UtcNow;
var local = utc.ToLocalTime();
// DateTimeOffset 自帶偏移,推薦跨時區場景使用

14.併發與異步

Task 異步編程、Parallel 並行、lock 同步與線程安全集合。

async / await

async 方法返回 Task;await 讓出線程、完成後恢復。期間線程可用於其他工作,不阻塞。

1
2
3
4
5
6
async Task<string> FetchAsync() {
// await 讓出線程,不阻塞
var data = await httpClient.GetStringAsync(url);
return data;
}
string result = await FetchAsync(); // 調用方也 await

Task 與返回值

Task 表示異步操作;Task<T> 帶返回值。Task.Run 把同步工作放線程池,ContinueWith 鏈式。

1
2
3
4
5
Task<int> t = Task.Run(() => Compute());
int result = await t;
var t2 = Task.Run(async () => await FetchAsync());
// Task.WhenAll:併發等待多個
var all = await Task.WhenAll(t, t2);

Parallel 並行

Parallel.For/ForEach 用線程池並行執行獨立工作。CPU 密集可多核利用;注意線程安全與過度並行。

1
2
3
4
5
Parallel.For(0, 100, i => {
Process(i); // 並行執行
});
Parallel.ForEach(items, item => Process(item));
// 控制最大並行度:new ParallelOptions { MaxDegreeOfParallelism = 4 }

lock 同步

lock 保證同一時刻只有一個線程進入臨界區。鎖對象通常是私有 readonly 字段,避免鎖 this。

1
2
3
4
5
6
7
8
private readonly object _lock = new();
int _counter = 0;
void Increment() {
lock (_lock) {
_counter++; // 原子:多線程安全
}
}
// lock = Monitor.Enter/Exit 的語法糖

Thread 線程

Thread 手動管理線程:Start 啓動、Join 等待。大多場景 Task/Parallel 更合適,Thread 用於長駐後台任務。

1
2
3
4
5
6
var thread = new Thread(() => {
Console.WriteLine("worker");
});
thread.IsBackground = true; // 後台線程:主線程退出即終止
thread.Start();
thread.Join(); // 等待完成

CancellationToken

CancellationToken 協作式取消:令牌觸發 IsCancellationRequested,async 方法拋 OperationCanceledException。

1
2
3
4
5
6
7
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
var data = await httpClient.GetStringAsync(url,
cts.Token);
try { await SlowAsync(cts.Token); }
catch (OperationCanceledException) { /* 已取消 */ }
// cts.Token.ThrowIfCancellationRequested() 主動檢查

併發集合

多線程安全集合:ConcurrentDictionary、ConcurrentQueue、ConcurrentBag。替代手動加鎖讀寫的普通集合。

1
2
3
4
5
6
var dict = new ConcurrentDictionary<string, int>();
dict.TryAdd("a", 1);
dict.TryUpdate("a", 2, 1); // 條件更新
var queue = new ConcurrentQueue<int>();
queue.Enqueue(1);
if (queue.TryDequeue(out int v)) { }

異步最佳實踐

全程 async(async all the way):別用 .Result 阻塞。ConfigureAwait(false) 在庫代碼避免回到同步上下文。

1
2
3
4
5
6
7
// 全程 await 鏈條
async Task<string> OuterAsync() {
var data = await InnerAsync().ConfigureAwait(false);
return data;
}
// 庫/非 UI 層用 ConfigureAwait(false)
// UI 事件處理器保留上下文(不加)

15.網絡與 HTTP

HttpClient 發起請求、JsonSerializer 序列化、WebSocket 與 Socket。

HttpClient 基礎

HttpClient 發 HTTP 請求。GetStringAsync 簡單文本、GetAsync 完整響應。HttpClient 應長期複用(單例)。

1
2
3
4
5
6
7
var http = new HttpClient();
string html = await http.GetStringAsync("https://example.com");
var resp = await http.GetAsync("https://example.com/api");
if (resp.IsSuccessStatusCode) {
string body = await resp.Content.ReadAsStringAsync();
}
// DI/IHttpClientFactory:推薦長期複用實例

POST 與 JSON

PostAsJsonAsync 發 JSON;PostAsync 發自定義內容。響應 ReadAsStringAsync 讀取。JsonSerializer 序列化。

1
2
3
4
5
6
7
var data = new { Name = "Nick", Age = 30 };
var resp = await http.PostAsJsonAsync(url, data);
// 讀取 JSON 響應:
var person = await resp.Content
.ReadFromJsonAsync<Person>();
// 手動序列化:
string json = JsonSerializer.Serialize(data);

請求頭與查詢參數

HttpRequestMessage 設 Headers(認證/User-Agent),URI 構建查詢字符串。注意默認 User-Agent 被拒的 API。

1
2
3
4
5
6
var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.Add("Authorization", "Bearer token");
req.Headers.Add("User-Agent", "MyApp/1.0");
var uri = new UriBuilder(url);
uri.Query = "key=value&page=2"; // 手動拼查詢串
var resp = await http.SendAsync(req);

System.Text.Json

JsonSerializer.Serialize/Deserialize 序列化 JSON。JsonSerializerOptions 配置大小寫/枚舉/忽略 null。

1
2
3
4
5
6
7
var opts = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
string json = JsonSerializer.Serialize(obj, opts);
var back = JsonSerializer.Deserialize<T>(json, opts);

調用 Web API

組合:構建請求 → 校驗狀態 → 反序列化。HttpStatusCode 檢查、ReadFromJsonAsync 一行完成。

1
2
3
4
5
6
7
using var resp = await http.GetAsync($"api/users/{id}");
if (resp.StatusCode == HttpStatusCode.NotFound) {
return null;
}
resp.EnsureSuccessStatusCode(); // 非 2xx 拋異常
var user = await resp.Content.ReadFromJsonAsync<User>();
return user;

WebSocket

ClientWebSocket 建立全雙工通道,SendAsync/ReceiveAsync 收發。適合實時推送(聊天、行情)。

1
2
3
4
5
6
7
8
using var ws = new ClientWebSocket();
await ws.ConnectAsync(uri, CancellationToken.None);
var buffer = new byte[1024];
var result = await ws.ReceiveAsync(buffer,
CancellationToken.None);
string msg = Encoding.UTF8.GetString(buffer, 0, result.Count);
await ws.SendAsync(payload, WebSocketMessageType.Text,
true, CancellationToken.None);

DNS 與 Socket

Dns.GetHostAddresses 解析域名;Socket 是底層傳輸。大多數應用用 HttpClient 就夠,Socket 用於自定義協議。

1
2
3
4
5
var ips = await Dns.GetHostAddressesAsync("example.com");
// 底層 Socket:
using var socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync("example.com", 80);

下載與流式處理

下載大文件用 Stream 流式寫入避免佔內存。HttpCompletionOption.ResponseHeadersRead 立即返回流。

1
2
3
4
5
using var resp = await http.GetAsync(url,
HttpCompletionOption.ResponseHeadersRead);
await using var stream = await resp.Content.ReadAsStreamAsync();
await using var file = File.Create("download.bin");
await stream.CopyToAsync(file); // 流式寫盤,不佔大內存

16.日期與時間

DateTime 與 TimeSpan、時區與偏移、格式化和解析。

DateTime 基礎

DateTime 表示日期時刻:Now 本地、UtcNow UTC、Today 日期。AddDays/AddHours 做日期運算。

1
2
3
4
5
DateTime now = DateTime.Now;
DateTime utc = DateTime.UtcNow;
DateTime today = DateTime.Today; // 當天 00:00
DateTime nextWeek = now.AddDays(7);
var str = now.ToString("yyyy-MM-dd HH:mm");

TimeSpan 時間間隔

TimeSpan 表示間隔:兩時間相減得到,FromDays/FromHours 構建,Days/Hours 拆分量。

1
2
3
4
5
TimeSpan duration = end - start;
double hours = duration.TotalHours; // 總小時(含天)
int h = duration.Hours; // 僅小時部分
var delay = TimeSpan.FromMinutes(5);
if (duration > TimeSpan.Zero) { }

DateTimeOffset

DateTimeOffset 帶時區偏移,跨時區推薦。與 UTC 互轉安全,避免本地時區歧義。

1
2
3
4
5
DateTimeOffset now = DateTimeOffset.Now;
DateTimeOffset utc = DateTimeOffset.UtcNow;
var offset = new DateTimeOffset(2024, 1, 1, 12, 0, 0,
TimeSpan.FromHours(8)); // +08:00
DateTime utcTime = now.ToUniversalTime();

DateTimeKind

DateTime 有 Kind:Unspecified/Local/Utc。未指定 Kind 的本地時間轉 UTC 會出錯,存儲用 Utc。

1
2
3
4
5
DateTime local = DateTime.Now; // Kind = Local
DateTime utc = DateTime.UtcNow; // Kind = Utc
DateTime utcFromLocal = local.ToUniversalTime();
// 避免:new DateTime(...) 默認 Unspecified
// 存儲/傳輸統一用 UTC 或 DateTimeOffset

解析與格式化

DateTime.Parse/ParseExact 解析;TryParse 安全。格式符 yyyy/MM/dd、HH:mm:ss、ffff 毫秒。

1
2
3
4
5
var d = DateTime.Parse("2024-01-01");
bool ok = DateTime.TryParse("2024-01-01", out var dt);
var exact = DateTime.ParseExact("01/02/2024",
"MM/dd/yyyy", CultureInfo.InvariantCulture);
string s = dt.ToString("O"); // ISO 8601 往返安全

時區轉換

TimeZoneInfo 做時區轉換:FindSystemTimeZoneById、ConvertTimeFromUtc。服務器通常存 UTC,展示時轉本地。

1
2
3
4
5
DateTime utc = DateTime.UtcNow;
TimeZoneInfo shanghai =
TimeZoneInfo.FindSystemTimeZoneById("Asia/Shanghai");
DateTime local = TimeZoneInfo.ConvertTimeFromUtc(utc, shanghai);
// 偏移固定用 DateTimeOffset 更簡單

Stopwatch 計時

Stopwatch 高精度測時(毫秒級)。Start/Stop/Elapsed/ElapsedMilliseconds,性能測量標配。

1
2
3
4
5
6
var sw = Stopwatch.StartNew();
DoWork();
sw.Stop();
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
// 或 sw.Elapsed.TotalMilliseconds
// Restart() 直接複用計時器

DateOnly 與 TimeOnly

DateOnly 僅表示日期、TimeOnly 僅表示時間(C# 10),無時區負擔,適合生日、日曆與排班等場景。

1
2
3
4
5
6
DateOnly today = DateOnly.FromDateTime(DateTime.Now);
DateOnly birthday = new(2000, 6, 15);
TimeOnly start = new(9, 30);
TimeOnly end = new(18, 0);
TimeSpan span = end - start; // 8.5 小時
Console.WriteLine(today); // 輸出今天日期

17.進程與系統

啓動外部進程、環境變量、路徑與系統信息。

啓動進程

Process.Start 啓動外部程序(shell 命令、其他可執行文件)。ArgumentList 傳參避免拼接注入。

1
2
3
4
5
6
var psi = new ProcessStartInfo("git") {
ArgumentList = { "log", "-1" }, // 參數數組,無注入
RedirectStandardOutput = true, // 捕獲輸出
};
using var proc = Process.Start(psi)!;
string output = await proc.StandardOutput.ReadToEndAsync();

環境變量

Environment.GetEnvironmentVariable 讀、SetEnvironmentVariable 寫(進程級)。GetEnvironmentVariables 全部。

1
2
3
4
5
string? path = Environment.GetEnvironmentVariable("PATH");
Environment.SetEnvironmentVariable("MY_VAR", "value");
// 系統級:
Environment.SetEnvironmentVariable("MY_VAR", "v",
EnvironmentVariableTarget.Machine);

系統信息

Environment 提供系統信息:OSVersion、MachineName、CurrentDirectory、ProcessorCount、TickCount。

1
2
3
4
5
string os = Environment.OSVersion.ToString();
string machine = Environment.MachineName;
string dir = Environment.CurrentDirectory;
int cores = Environment.ProcessorCount;
string user = Environment.UserName;

命令行解析

args 傳入命令行參數;Environment.GetCommandLineArgs 取完整(含程序名)。命令行工具參數解析。

1
2
3
4
5
6
7
// Program.cs 頂層
if (args.Length == 0) {
Console.Error.WriteLine("usage: app <file>");
return 1;
}
// 前兩個字符是 -- 視為選項(自行實現)
foreach (var a in args.Skip(1)) { /* 處理 */ }

退出與信號

Environment.Exit(1) 立即退出;ExitCode 設置退出碼。AppDomain.ProcessExit 事件做清理。

1
2
3
4
5
AppDomain.CurrentDomain.ProcessExit += (s, e) => {
SaveState(); // 退出前清理
};
Environment.ExitCode = 2; // 讓運行時自然退出
// 或直接 Environment.Exit(0)

枚舉進程

Process.GetProcesses 枚舉系統進程,讀取 Id/ProcessName/WorkingSet64 等信息,Kill 結束進程。

1
2
3
4
5
6
foreach (var p in Process.GetProcesses()) {
Console.WriteLine($"{p.Id}: {p.ProcessName}");
}
var current = Process.GetCurrentProcess();
long mem = current.WorkingSet64;
Process.GetProcessById(pid)?.Kill(); // 結束指定進程

Windows 註冊表

Microsoft.Win32.Registry 讀寫註冊表(僅 Windows)。GetValue/SetValue 訪問鍵值,需適當權限。

1
2
3
4
5
6
7
8
using Microsoft.Win32;
// 讀:
object? val = Registry.CurrentUser.OpenSubKey("Software\\App")
?.GetValue("Setting");
// 寫:
using var key = Registry.CurrentUser.CreateSubKey("Software\\App");
key.SetValue("Setting", 42);
// 注意:僅 Windows 可用,跨平台應用避免依賴

應用程序路徑

AppContext.BaseDirectory 是程序運行目錄,Environment.ProcessPath 是當前進程路徑,定位資源文件用它們。

1
2
3
4
string baseDir = AppContext.BaseDirectory; // 程序運行目錄
string? path = Environment.ProcessPath; // 當前進程完整路徑
string file = Path.Combine(baseDir, "data.json");
// 相對路徑依賴工作目錄,易受調用方影響,儘量用 BaseDirectory

18.正則表達式

Regex 匹配、捕獲組、替換與常用模式。

Regex 基礎

Regex.IsMatch 判斷、Matches 找全部、Match 找第一個。用 @ 原義字符串避免雙重轉義。

1
2
3
4
5
6
var r = new Regex(@"^\d{3}-\d{4}$");
bool ok = r.IsMatch("123-4567");
foreach (Match m in Regex.Matches(text, @"\b\w+\b")) {
Console.WriteLine(m.Value);
}
// @ 原義:\d 是正則的 \d,不是字符串轉義

捕獲組

圓括號捕獲子串;Groups[1]、命名組 (?<name>...) 用 Groups["name"]。捕獲用於提取字段。

1
2
3
4
5
6
7
var r = new Regex(@"(\d{4})-(\d{2})-(\d{2})");
var m = r.Match("date: 2024-01-01");
if (m.Success) {
string year = m.Groups[1].Value; // 2024
}
// 命名組:Regex(@"(?<year>\d{4})-")
string y = m.Groups["year"].Value;

替換

Regex.Replace 用模式替換,$1/$2 引用捕獲組。可用於格式化、脱敏、清理文本。

1
2
3
4
5
string masked = Regex.Replace(
phone, @"(\d{3})\d{4}(\d{4})", "$1****$2");
string cleaned = Regex.Replace(
text, @"[\s\t]+", " ");
// 替換回調:Regex.Replace(text, pat, m => Process(m.Value))

RegexOptions

RegexOptions.IgnoreCase 忽略大小寫、Multiline 讓 ^/$ 匹配行、Compiled 編譯加速重複使用。

1
2
3
4
5
var r = new Regex(@"^[a-z]+$",
RegexOptions.IgnoreCase | RegexOptions.Multiline);
// Compiled:正則被多次使用才值得
var fast = new Regex(@"pattern", RegexOptions.Compiled);
// IgnorePatternWhitespace 允許模式內註釋

常用模式

郵箱、URL、IP、手機號的常用正則。注意:複雜校驗(真郵箱)用專門庫而非正則。

1
2
3
4
var email = @"^[^@\s]+@[^@\s]+\.[^@\s]+$";
var ipv4 = @"^(?:\d{1,3}\.){3}\d{1,3}$";
var url = @"^https?://[^\s]+$";
// 簡單校驗夠用;生產級校驗用專門庫

量詞與錨點

* 零或多次、+ 一次或多次、? 零或一次、{n,m} 指定次數;^ 行首、$ 行尾、\b 詞邊界。

1
2
3
4
5
6
@"\d+" // 一個或多個數字
@"colou?r" // color 或 colour
@"\d{2,4}" // 2 到 4 位數字
@"^start" // 以 start 開頭(行首)
@"end$" // 以 end 結尾
@"\bword\b" // 獨立單詞 word

斷言與前視後視

零寬斷言不消耗字符:(?<=...) 後視、(?=...) 前視、(?!...) 否定前視,用於匹配位置條件。

1
2
3
4
5
6
7
8
// 後視 (?<=@):提取 @ 後面的郵箱域名部分
string domain = Regex.Match("[email protected]",
@"(?<=@)[^@]+").Value; // example.com
// 前視 (?=\d):判斷字母后是否緊跟數字
bool hasDigit = Regex.IsMatch("a1", @"[a-z](?=\d)"); // true
// 否定前視 (?!...):匹配不是 world 開頭的單詞
var first = Regex.Match("hello world",
@"\b(?!world)\w+").Value; // hello

回溯與 ReDoS

嵌套量詞與備選分支會引發回溯,惡意輸入可造成指數級匹配(ReDoS),需限制超時或禁用回溯。

1
2
3
4
5
6
7
8
// 危險:嵌套量詞在長輸入上可能指數級回溯
// var risky = new Regex(@"(a+)+$");
// 緩解一:總是設置匹配超時
var withTimeout = new Regex(@"(a+)+$", RegexOptions.None,
TimeSpan.FromSeconds(2));
// 緩解二:NonBacktracking 禁用回溯(.NET 7+)
var safe = new Regex(@"(a+)+$", RegexOptions.NonBacktracking);
// 用户輸入的正則或文本場景務必做超時與長度限制

19.構建與調試

dotnet CLI、項目配置、調試、日誌與條件編譯。

dotnet CLI

dotnet build 編譯、run 運行、test 測試、publish 發佈。--configuration Release 發佈配置。

1
2
3
4
5
// $ dotnet build # 編譯 Debug
// $ dotnet run # 編譯並運行
// $ dotnet test # 運行測試
// $ dotnet publish -c Release -o out
// $ dotnet clean # 清空產物

csproj 配置

csproj 控制構建:TargetFramework、Nullable、ImplicitUsings、PackageReference 依賴、LangVersion。

1
2
3
4
5
6
7
8
9
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

調試與日誌

Debug.WriteLine 僅 Debug 構建;Trace 都可用。ILogger 記錄日誌級別。Console.WriteLine 調試臨時用。

1
2
3
4
5
Debug.WriteLine($"value={x}"); // 僅 Debug 構建
Trace.TraceInformation("start"); // 全部構建
var logger = LoggerFactory.Create(b =>
b.AddConsole()).CreateLogger("App");
logger.LogInformation("Processing {Id}", id);

條件編譯

#if DEBUG / #elif / #endif 按符號裁剪代碼。項目級 DefineConstants 自定義符號。

1
2
3
4
5
6
#if DEBUG
Console.WriteLine("debug build");
#elif RELEASE
Console.WriteLine("release build");
#endif
// csproj:<DefineConstants>TRACE;MY_FLAG</DefineConstants>

單元測試

xUnit/NUnit/MSTest 斷言行為。[Fact] 測試方法、Assert.Equal 斷言。dotnet test 運行。

1
2
3
4
5
6
7
[Fact]
public void Add_ReturnsSum() {
var calc = new Calculator();
var result = calc.Add(2, 3);
Assert.Equal(5, result);
}
// xUnit 測試類:public class CalculatorTests

NuGet 包管理

dotnet add package 添加依賴,restore 還原。PackageReference 在 csproj 記錄版本。

1
2
3
4
// $ dotnet add package Newtonsoft.Json
// $ dotnet restore # 還原依賴
// $ dotnet list package # 查看依賴
// 依賴記錄在 csproj 的 PackageReference

發佈與單文件

dotnet publish 發佈生產版本;PublishSingleFile 打包單文件、SelfContained 免安裝運行時。

1
2
3
4
5
6
7
// 發佈 Release 到 out 目錄
// $ dotnet publish -c Release -o out
// 打包單文件(目標機需安裝 .NET):
// $ dotnet publish -r win-x64 -p:PublishSingleFile=true -o out
// 自包含(含運行時,目標機免安裝,體積更大):
// $ dotnet publish -r win-x64 --self-contained true -o out
// 裁剪未用代碼減小體積:-p:PublishTrimmed=true

靜態分析器

Roslyn 分析器在編譯期檢查代碼質量與潛在缺陷,警告可升級為錯誤,阻止帶問題的構建發佈。

1
2
3
4
5
6
// 分析器在編譯期報告代碼質量與潛在缺陷
class AnalyzerDemo {
public int Add(int a, int b) => a + b;
}
// 把警告視為錯誤:dotnet build -warnaserror
// 啓用內置質量分析器:csproj 裏設 AnalysisLevel 為 latest

官方鏈接

直達官方文檔與資源。

關於本速查

本頁是 C# 12(.NET 8)的自包含速查手冊,覆蓋語言核心、BCL 常用類型與異步編程在真實項目中約 80% 的常見用法。內容偏向現代慣用法:屬性與自動屬性、LINQ、async/await、record 類型、模式匹配、可空引用類型。C# 由 Anders Hejlsberg 於 2000 年隨 .NET 平台推出,是 Windows 生態、Unity 遊戲開發與後端服務的核心語言,強調類型安全與生產力和諧統一。 19 個章節各自聚焦一個主題:基礎語法、變量、類型與引用/值語義、控制流、函數、字符串、集合、內存管理(GC)、面向對象、錯誤處理、輸入輸出、常見誤區、併發、網絡、時間、進程、正則與構建工具。每個小節都配有「概念介紹 + 可直接複製的代碼片段」。 所有代碼與文字均在瀏覽器本地渲染,無任何數據離開你的設備。權威參考見 Microsoft Learn 官方文檔。

版本 2.1.0