C# 基础知识参考字典

要点

这是查的不是读的:按用途分六组收录 C# 高频条目——类型系统、集合与 LINQ、异步、record 与模式匹配、常用特性、常见报错。每条给签名、一句话语义、最小示例和坑。概念背景在《C# 语法要点》,本文只做速查。版本口径:.NET 10 / C# 14。

一、类型系统

组内共性:C# 类型分值类型(struct,栈语义)与引用类型(class,堆语义)两族,泛型约束决定类型参数能干什么。

string 与内插

var name = "go"; var n = 3;
var s = $"{name,-5}|{n:D3}";      // "go   |003",逗号后是对齐与格式说明符
var raw = """C:\temp\{x}""";      // 原始字符串字面量,反斜杠不转义、单花括号不内插

string 不可变,所有”修改”方法都返回新串。坑:循环里 += 拼串是 O(n²),改用 StringBuilder。

struct / record struct:值语义

public readonly record struct Point(int X, int Y);  // 值相等 + 不可变 + 无堆分配
var a = new Point(1, 2); var b = a; b.X = 9;         // b 是副本,a 不变

16 字节以下、生命周期短的数据用值类型省 GC;坑:作为接口变量传递会装箱(boxing),性能优势瞬间反转。

泛型约束 where

T Create<T>() where T : class, new() => new();   // 引用类型 + 有无参构造
约束 含义 坑
where T : class 引用类型 还能传 null,加 notnull 才禁止
where T : struct 非空值类型 不能与 new() 同用(本身可 default)
where T : new() 有无参构造 靠反射创建,热路径慎用
where T : ISearch 实现某接口 可同时约束多个接口
where T : unmanaged 非托管类型 interop / unsafe 场景专用

二、集合与 LINQ 操作符

组内共性:集合类解决”怎么存”,LINQ 解决”怎么查”;两者都以 IEnumerable 为通用货币。

三件套怎么选

类型 查找复杂度 坑
List 按索引 O(1),按值 O(n) 频繁 Contains 就是选错了容器
Dictionary O(1) 键重复抛异常;遍历时改集合抛 InvalidOperationException
HashSet O(1) 只判存在不存值;容量预估不当会频繁扩容

Where / Select / SelectMany

var urls = links.Where(l => l.Clicks > 100).Select(l => l.Url).ToList();
var tags = posts.SelectMany(p => p.Tags).Distinct().ToList();  // 摊平嵌套,join 的反面

坑:不加 ToList 返回的是惰性 IEnumerable,每次遍历重新执行——见语法篇”延迟执行”。

First 家族:空序列行为是分水岭

var a = list.First(x => x.N > 0);        // 空 → 抛 InvalidOperationException
var b = list.FirstOrDefault(x => x.N > 0); // 空 → 返回 default(引用类型为 null)

坑:FirstOrDefault 拿到 null 分不清”没有”还是”元素本身是 null”;值类型加 Second 检查或用 TryGet 模式。Single 用于”必须有且仅有一个”,查出两个也抛——用它表达唯一性约束。

ToDictionary / ToLookup / GroupBy

var byCode = links.ToDictionary(l => l.Code);          // 键重复直接抛
var grouped = links.ToLookup(l => l.Domain);          // 键重复不抛,多值成组
var q = links.GroupBy(l => l.Domain);                  // 惰性,遍历才分组

坑:源数据可能重键时 GroupBy/ToLookup 是安全牌,ToDictionary 是断言。

Any / All / Contains / Aggregate / Chunk

if (cart.Any(i => i.Price > 1000)) { ... }   // 存在性判断,别用 Count() > 0(要数完)
var total = cart.Aggregate(0m, (sum, i) => sum + i.Price);  // 折叠
foreach (var batch in ids.Chunk(500)) { ... }  // .NET 6+,分批防 IN 子句过长

坑:Count() 谓词版与 Any 语义可互换时,Any 找到即停。

三、异步:Task、async 流、取消

组内共性:异步三问——等谁(Task)、怎么取消(CancellationToken)、怎么流式拿(IAsyncEnumerable)。

Task.WhenAll / WhenAny

var results = await Task.WhenAll(urls.Select(FetchAsync));  // 全部成功才返回,异常在 await 处抛
var firstDone = await Task.WhenAny(tasks);                  // 任一完成,常配超时

坑:WhenAll 一个失败不取消其余任务;要”失败即止损”用 WhenAny + 异常检查的组合或 CancellationTokenSource。

CancellationToken:取消是协作不是强杀

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
var page = await httpClient.GetStringAsync(url, cts.Token);  // token 一路传到底
cts.Token.ThrowIfCancellationRequested();                    // 长循环里主动检查点

坑:注册了 token 却不传给底层 IO,取消不会生效;ASP.NET Core 里 RequestAborted 就是客户端断连信号,长任务记得挂上。

IAsyncEnumerable 与 await foreach

async IAsyncEnumerable<Link> StreamLinksAsync(CancellationToken ct)
{
    foreach (var l in await LoadPageAsync(ct))
    {
        await Task.Delay(10, ct);          // 模拟流式产出
        yield return l;                     // async 流用 yield,消费方按需拉
    }
}
await foreach (var link in StreamLinksAsync(ct)) Process(link);

对应同步版的 IEnumerable + yield return;适合分页抓取、日志 tail。坑:不用 await foreach 而用普通 foreach 会编译错误——它只能被异步消费。

Task.Run 的适用边界

var data = await Task.Run(() => CpuHeavyParse(text));  // 把 CPU 密集活挪到线程池

坑:把纯 IO 包进 Task.Run 是反模式——不省任何东西还占线程;服务器上 Task.Run 做发后不理,进程退出任务即丢,需要可靠后台处理用 Channel + BackgroundService(实战篇有完整例子)。

四、record 与模式匹配

组内共性:record 管数据形状,模式匹配管形状分支,二者是同一套建模思路的两面。

record:三种形态

public record Link(string Code, string Url);          // 位置参数:自动出 init 属性 + 解构
public record struct Size(int W, int H);              // 值语义 record(C# 10+)
public record CacheEntry(string Url, DateTime At);    // 引用语义,拷贝成本低

坑:record 的相等比较逐字段调用 Equals,含大型集合字段时比较成本可观。

with 与解构

var updated = entry with { At = DateTime.UtcNow };   // 浅拷贝改字段,集合字段仍是同一引用
var (code, url) = link;                               // 解构成元组

坑:with 是浅拷贝,嵌套可变引用类型不会被复制。

switch 表达式与四类模式

string Classify(object o) => o switch
{
    Link { Clicks: >= 1000 } hot  => $"热门:{hot.Code}",  // 属性 + 关系模式
    Link l                        => $"普通:{l.Code}",    // 类型模式 + 变量捕获
    int and < 0                  => "负数",              // 类型与关系模式用 and 组合
    _                             => "未知"
};

// 列表模式(C# 11+)要求输入是集合,别打在 object 上
string ParseCmd(string[] argv) => argv switch
{
    ["run", var target] => $"运行 {target}",
    ["help", ..]        => "帮助",
    _                   => "未知"
};

坑一:switch 表达式必须穷尽或落 _,漏分支是编译错误不是运行时隐患——这正是它比 if-else 链值钱的地方,新增 record 成员时编译器会把漏掉的分支顶出来。坑二:关系与列表模式对输入的静态类型有要求,打在 object 上直接编译失败,先用类型模式收窄再套。

五、常用属性特性

组内共性:特性(attribute)是贴在代码元素上的元数据,由编译器、运行时或库在各自阶段读取。

特性 谁消费 典型用途
[Required] / [Range] / [StringLength] DataAnnotations 校验器 模型校验,API 入参自动 400
[JsonPropertyName(“code”)] System.Text.Json 改 JSON 字段名而不改 C# 命名
[Obsolete(“用 NewApi 代替”, error: true)] 编译器 废弃 API,error: true 直接编译失败
[DebuggerDisplay(“Code={Code}”)] 调试器 断点悬停显示有意义的摘要
[Fact] / [Theory](xUnit) 测试框架 标记测试方法;Theory 配 [InlineData]
[HttpGet(“/s/{code}”)] ASP.NET Core 路由 控制器场景声明路由模板

坑:[Required] 只在触发校验管线时生效(控制器模型绑定、Manual Validation),Minimal API 手写端点不会自动校验。

六、常见报错速查

报错/警告 含义 处置
NullReferenceException 运行时解引用了 null NRT 只是编译期分析,JSON/HTTP 边界数据仍要运行时校验;栈里第一行用户代码就是案发现场
CS8602 警告 可能为 null 的值被解引用 别用 null! 硬压:先判空或用 ?. / ?? 收敛类型
CS8619 / CS8625 警告 可空性不匹配(把可空赋给非空等) 接口边界改 T?,实现处补判空
InvalidOperationException: Sequence contains no elements 对空序列调 First / Single / Max 可能为空就 FirstOrDefault,且检查 null
ArgumentException: same key has already been added ToDictionary / Dictionary.Add 键重复 indexer 赋值可覆盖,语义不同;重键数据用 ToLookup
CS4014 警告 await 的 Task 没接住 要么 await,要么显式丢弃 _ = Task,别让异常无声消失
CS1998 警告 async 方法里没有 await 去掉 async 直接返回 Task.FromResult,或补真 await
CS8805 顶级语句与显式 Main 共存 二选一,见第一个应用篇
CS0246 类型找不到 缺 using 或缺包引用 ImplicitUsings 不含第三方命名空间;先 dotnet add package 再 using

全量速查索引

条目 一句话用途 最大的坑
字符串内插 / 原始字符串 格式化与免转义字面量 循环 += 拼串 O(n²)
record struct 小数据值语义 装箱即失效
where 约束 给类型参数立法 class 约束仍可空
Dictionary / HashSet O(1) 键值 / 存在性 遍历中修改集合
SelectMany 摊平嵌套 语义是笛卡尔不是 join
FirstOrDefault 安全取首个 null 语义二义
ToDictionary / ToLookup 建索引 / 分组索引 前者重键抛异常
Any 存在性判断 用 Count() > 0 替代它要数全量
Task.WhenAll 并发聚合 失败不取消兄弟任务
CancellationToken 协作式取消 不传到底层就形同虚设
IAsyncEnumerable 异步流式产出 只能 await foreach
Task.Run CPU 密集挪线程池 包 IO 是反模式
with 表达式 非破坏更新 浅拷贝
switch 表达式 形状分支 必须穷尽(这是优点)
[Required] 等 声明式校验 管线不触发就不生效

延伸阅读

概念不明白的回《C# 语法要点》;

版本核实(以官方文档为准):本文条目以 .NET 10(当前 LTS)/ C# 14 为基准;Chunk、DistinctBy 为 .NET 6+,record struct 为 C# 10+,列表模式与 required 成员为 C# 11+,原始字符串字面量为 C# 11+,field 关键字为 C# 14;可空引用类型相关警告码(CS86xx)自 .NET 6 起默认启用。具体以官方文档为准。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇