C# 语法速查表 - C# 常用语法与 .NET 开发大全

这套速查给用 .NET 写 Web API、服务或桌面应用的开发者,聚焦每天都会写的 C# 语法:带访问修饰符的类与接口、取代手写循环的 LINQ 查询、处理 I/O 密集的 async/await、泛型与委托,以及属性与事件模型。与平铺的 C# 特性表不同,条目按语言构造来分,并点出 C# 和 Java/JS 不一样的地方。读完应能用一条组合 LINQ 查询代替嵌套循环、写出不会死锁的异步方法,并用属性、事件和委托把类型设计清楚。

编程语言·共 39 条命令·最后更新 2026-07-21
csharp.netlinq

典型使用场景

面向用 C# 写服务端与桌面应用的开发者。本表把语法表面最容易忘的部分集中在一起:类型声明从 class/interface/sealed/abstract 到 record 的选型、属性的 get/set/init/表达式体三种写法、委托(delegate/Action/Func/Predicate)与事件的分工、LINQ 的过滤到聚合链路、以及 async/await 与 CancellationToken 的协作方式。适合刚接手的项目要快速判断「这里该用 record 还是 class」「这个回调该声明成 Func 还是事件」时按需回查。

类与接口 7

public class MyClass { }
类定义
public interface IMyInterface { void Method(); }
接口定义
public class Derived : Base, IMyInterface { }
继承与实现接口
public abstract class AbstractClass { }
抽象类
public sealed class SealedClass { }
密封类(不可继承)
public static class Utils { }
静态类
public record Person(string Name, int Age);
记录类型(C# 9)

属性与方法 6

public int Age { get; set; }
自动属性
public string Name { get; init; }
只初始化属性(C# 9)
public int Age => _age;
只读属性(表达式体)
public void Method() { }
方法定义
public int Add(int a, int b) => a + b;
表达式体方法
public event EventHandler MyEvent;
事件定义

LINQ 查询 8

var result = list.Where(x => x > 5);
过滤
var result = list.Select(x => x * 2);
映射
var result = list.OrderBy(x => x.Age);
排序
var result = list.GroupBy(x => x.Category);
分组
var result = list.First(x => x.Id == 1);
查找第一个
var result = list.Any(x => x > 0);
是否存在
var result = list.Sum(x => x.Price);
求和
var result = list.Aggregate((a, b) => a + b);
聚合

异步编程 6

public async Task<int> GetDataAsync() { }
异步方法
await Task.Delay(1000);
异步延迟
var result = await httpClient.GetStringAsync(url);
异步 HTTP 请求
Task.Run(() => { });
后台线程执行
await Task.WhenAll(task1, task2);
并行等待多个任务
CancellationTokenSource cts = new();
取消令牌

泛型与委托 6

public class Stack<T> { }
泛型类
public T Max<T>(T a, T b) where T : IComparable<T> { }
泛型约束
public delegate void MyDelegate(string msg);
委托定义
public event Action<string> OnMessage;
Action 委托
public Func<int, int, int> add = (a, b) => a + b;
Func 委托
public Predicate<int> isPositive = x => x > 0;
Predicate 委托

集合与字典 6

List<int> list = new() { 1, 2, 3 };
列表
Dictionary<string, int> dict = new();
字典
HashSet<int> set = new() { 1, 2, 3 };
哈希集合
Queue<int> queue = new();
队列
Stack<int> stack = new();
栈
var tuple = (1, "hello");
元组

参数矩阵

参数作用示例
record / init不可变数据类型:位置参数构造 + 只初始化属性,适合传输对象public record Person(string Name, int Age);
Func<T, TResult> / Predicate<T>有返回值 / 返回 bool 的委托,分别对应映射与条件判断Func<int, int, int> add = (a, b) => a + b;
CancellationTokenSource cts = new()取消令牌源:把 cts.Token 传入异步方法,配合 cts.Cancel() 中断长任务CancellationTokenSource cts = new();
async / await / Task.WhenAll非阻塞并发:WhenAll 并行等待多个任务,比逐个 await 快await Task.WhenAll(task1, task2);

易错点与避坑指南

现象对象创建后想改属性,编译器报「只能赋值一次」的错误。

原因record 与 init 属性在初始化后不可再赋值,这是设计意图而非缺陷。

处置确实需要可变状态时改用 class + get/set 属性;只在传输层用 record 固化数据形状。

现象在 async 方法里忘了 await,异常没抛出,任务静默失败。

原因async 方法返回 Task,不 await 就等于没执行;异常被包进 Task 里无人观察。

处置每个 async 调用都加 await,或把返回的 Task 交给 await Task.WhenAll(...) 统一等待。

现象循环里请求接口越跑越慢,界面卡住。

原因在 async 方法里直接调用 Task.Run 或同步阻塞的 I/O,线程被占住。

处置I/O 直接 await(await httpClient.GetStringAsync(url)),并发交给 Task.WhenAll,而不是 Task.Run。

现象声明了 public event 却没人能赋值,外部无法触发。

原因事件默认只能在声明它的类内部触发,外部只能 += / -= 订阅。

处置若外部也要触发,改用 Action<string> 这类普通委托字段并提供 Raise 方法。

排障路径

  1. 1把一组异步调用改成真正的并发

    await Task.WhenAll(task1, task2);

    逐个 await 是串行的;WhenAll 让两个请求同时在飞,耗时接近较慢的那个。

  2. 2给长任务加上可取消能力

    CancellationTokenSource cts = new();

    创建源后把 cts.Token 传给 async 方法,调用方在 cts.Cancel() 时让方法体退出。

  3. 3确认集合里有元素再取值,避免空引用

    var result = list.Any(x => x > 0);

    Any 先做存在性判断,再配合 First / Aggregate 取值,比直接 FirstOrDefault 更安全。

命令示例

record + init 表达不可变 DTO

public record Person(string Name, int Age); var p = new Person("Ada", 36) with { Age = 37 };

record 自动生成值相等、ToString 与解构,with 表达式基于原值复制并覆盖指定字段——这是「改一个字段,其它全保留」的惯用法。属性写成 init 后只能在构造或 with 里赋值,误改会直接编译失败,而不是在运行期污染对象。

LINQ 链:先过滤再排序求和

var total = orders .Where(x => x.Status == "paid") .OrderByDescending(x => x.Age) .Select(x => x.Price) .Sum();

LINQ 方法返回的是 IEnumerable,链式调用不会立刻计算数据,写完链才执行。因此把 Where 放在 Sum 之前能显著减少参与运算的元素;而对 IQueryable(EF Core)来说,链的顺序同时决定生成的 SQL,顺序错了性能会差一个数量级。

带约束的泛型方法

public T Max<T>(T a, T b) where T : IComparable<T> { return a.CompareTo(b) >= 0 ? a : b; }

where T : IComparable<T> 约束让方法体内可以安全调用 CompareTo。没有约束时 T 只能做 == 这类无意义比较,想排序就得先强转装箱。约束是编译期检查,调用方传入不满足的类型会直接编译不过,而不是运行期抛异常。

Task.WhenAll 并行 + CancellationTokenSource

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var t1 = httpClient.GetStringAsync(url1, cts.Token); var t2 = httpClient.GetStringAsync(url2, cts.Token); string[] results = await Task.WhenAll(t1, t2);

await Task.WhenAll(...) 一次性等待多个任务,并发度真正提上去;using 保证 cts 被释放。传入 CancellationToken 后超时由调用方控制,避免一个慢请求把整批任务挂死——这是让并行调用可取消的关键,漏掉就只能等默认超时。

用泛型集合替代无类型容器

List<int> list = new() { 1, 2, 3 }; Dictionary<string, int> dict = new() { ["id"] = 1 }; HashSet<int> seen = new() { 1, 2, 3 }; Queue<int> queue = new(); Stack<int> stack = new();

用 List<T> / Dictionary<TKey,TValue> 代替 ArrayList / Hashtable:编译期就能发现类型错误,泛型集合不装箱,值类型性能差距往往在一到两个数量级。Hashtable 允许 key 为 null 且比较用 Equals,Dictionary 两者都禁止——迁移时最容易出行为差异。

常见坑与注意事项

  • async/await 方法里出现裸的 .Result 或 .Wait(),在同步上下文(WinForms/WPF/ASP.NET 经典管道)里会死锁:UI 线程等结果,被等的方法又要回 UI 线程继续。统一用 await,这是最难排查的一类挂起。
  • async Task 方法没有以 await 结尾时编译器只发警告,异常会被静默吞掉,任务变成「已完成但什么都没做」。别用空 Task.FromResult 掩盖,也不要靠 try/catch 捕获一个永远不会抛的 await。
  • LINQ 链里的每一步都是延迟执行。把它赋给变量却忘了终结操作(Count/ToList/foreach)时,查询一次都不执行,排查时表现为「代码明明写了却没效果」。
  • var 只在右侧类型明确时才安全:var d = GetData(); 若返回类型被改动(如 IEnumerable 变 List),编译通过但运行期行为或性能悄悄变化。跨版本演进的数据结构不要用 var 承接。
  • Task.Run 里访问 UI 控件或 HttpClient 之外的非线程安全对象会抛异常。CPU 密集计算才用 Task.Run;I/O 密集应直接 await,否则会白白占用线程池并引入竞态。
  • 开启 C# 8+ 的可空引用类型后,GetValueOrDefault / 显式 null 检查漏写只是警告不是错误,最终变成运行期 NullReferenceException。升级项目时把警告级别提到 error 再逐个修,否则是埋雷。

提示

  • LINQ 查询延迟执行,只在枚举结果时才真正执行。
  • async/await 是异步编程的核心,避免阻塞 UI 线程。
  • C# 9 引入 record 类型,适合不可变数据模型。

常见问题

Func、Action 和 Predicate 三种委托该怎么选?

看返回值和参数列表。Func<int, int, int> 表示有入参也有返回值,最后一个类型是返回值类型,本例中 add = (a, b) => a + b 是一个两参一返回的函数。Action<string> 只有入参没有返回值,适合事件回调这种「通知一声就完事」的场景,本表里 public event Action<string> OnMessage 就是这个用法。Predicate<int> 专门接收一个参数并返回 bool,isPositive = x => x > 0 属于这一类,通常用来把判断逻辑当值传递,例如交给 Where 之类的过滤入口,而不是写成完整的条件方法。

sealed、abstract 和 static 三个修饰符分别解决什么问题?

三个都是在收窄或固定类型的能力边界。public sealed class SealedClass 表示不可被继承,适合那些行为已经完整、不打算留给子类扩展的类型,密封后编译器还能对被调用的成员做更好的优化。public abstract class AbstractClass 表示只能被继承、不能直接实例化,用来承载一组子类必须实现的契约。public static class Utils 则完全相反——不可继承也不可实例化,只能直接拿它的静态成员,用作无状态的工具集合。选型时先问这个类型是「给别人实现」「封死不让扩展」还是「只提供一组函数」,答案直接对应其中一个修饰符。

where T : IComparable<T> 这类泛型约束什么时候必须加?

约束用来向编译器保证类型参数具备某些能力,编译器据此才能接受泛型体内的操作。本表的 public T Max<T>(T a, T b) where T : IComparable<T> 就是典型:如果方法体里要比较 a 和 b,就必须要求 T 实现 IComparable<T>,否则编译器无法确定 > 运算符是否可用。不写约束时编译器只能把 T 当成 object,比较、算术这类操作一律报错。加约束换来的是类型安全与更好的性能——编译器有机会为具体的值类型生成特化代码,而不是把一切都装箱成 object。

几个异步请求想同时跑,怎么写才不是串行等待?

await Task.WhenAll(task1, task2)。逐个 await 会让第二个请求等第一个返回才发出,网络往返时间被串起来;WhenAll 传入多个已启动的 Task,一次性等待它们全部完成,总耗时接近其中最慢的那个。参数可以多于两个,也可以直接传入 Task.WhenAll(tasks) 处理一个集合。前提是这些 Task 必须在调用 WhenAll 之前就已经启动——async 方法在第一次 await 处就返回控制权,所以先拿到几个 Task 变量、再统一 WhenAll,才是真正的并发。

Dictionary、HashSet、Queue、Stack 和元组分别适合什么数据?

按「要做什么操作」选。Dictionary<string, int> 存键值对,适合按 key 查找、修改与去重。HashSet<int> 只保留唯一元素,判断某值是否已存在是 O(1),适合过滤重复。Queue<int> 是先进先出,用于按到达顺序处理任务。Stack<int> 后进先出,用于回溯、撤销这类需要回到最近一步的场景。var tuple = (1, "hello") 则不是容器:它把少量强关联的值轻量绑定在一起,适合从一个方法一次返回两个结果,省去定义一个只为传值的类。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们