本工具使用的开源库

本工具代码中捆绑了 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