Skip to content

C# 速查表

用于 .NET、Web 和游戏的现代面向对象语言。

01

入门

Hello World

C# 程序从 Main() 开始。using System; 导入 System 命名空间(Console、Math 等)。C# 9+ 支持顶级语句:只有 Console.WriteLine("Hello"); 的文件是有效程序。编译器自动生成类和 Main。

csharp
using System;

class Program {
    static void Main() {
        Console.WriteLine("Hello, World!");
    }
}

变量与类型

C# 是静态类型的。var 让编译器推断类型(编译时仍然类型安全)。decimal(m 后缀)用于需要精确精度的金融计算。const 是编译时常量;readonly 是在构造函数中设置的运行时常量。

csharp
int age = 30;
double pi = 3.14159;
decimal price = 19.99m;
char grade = 'A';
bool isDev = true;
string name = "Alice";
var x = 42;  // implicitly typed (int)
const double TAX = 0.08;

字符串插值

字符串插值($"...")在花括号中嵌入表达式。冒号后的格式说明符控制输出(F2 表示 2 位小数,yyyy-MM-dd 表示日期)。逐字字符串(@"")按字面值处理反斜杠——适用于文件路径和正则表达式。组合两者:$@"..."。

csharp
string name = "Alice";
int age = 30;
Console.WriteLine($"Name: {name}, Age: {age}");

// Formatting
double pi = 3.14159;
Console.WriteLine($"Pi: {pi:F2}");  // Pi: 3.14
Console.WriteLine($"Date: {DateTime.Now:yyyy-MM-dd}");

// Verbatim strings (@"") preserve backslashes
string path = @"C:\Users\name";

输入与输出

Console.ReadLine() 读取整行作为字符串。int.Parse 转换但无效输入时抛出异常;int.TryParse 更安全——返回 bool 并使用 out 参数。用户输入始终使用 TryParse 以避免坏数据导致的异常。

csharp
Console.Write("Enter name: ");
string name = Console.ReadLine();
Console.Write("Enter age: ");
int age = int.Parse(Console.ReadLine());

Console.WriteLine($"Hi {name}, you are {age}");

// Safe parsing with TryParse
if (int.TryParse(Console.ReadLine(), out int num)) {
    Console.WriteLine($"Valid: {num}");
} else {
    Console.WriteLine("Invalid number");
}

可空类型

int? 是可空值类型(Nullable<int>)。?. 运算符(null 条件)安全访问成员——返回 null 而不是抛出异常。?? 运算符(null 合并)为 null 提供默认值。C# 8+ 可空引用类型在编译时警告潜在 null。

csharp
int? maybeAge = null;  // nullable int
if (maybeAge.HasValue) {
    Console.WriteLine(maybeAge.Value);
}

string? maybeName = null;  // nullable reference (C# 8+)
int length = maybeName?.Length ?? 0;  // null-conditional + null-coalescing

// ValueOrDefault
int age = maybeAge ?? 0;  // 0 if null
02

字符串

字符串方法

C# 字符串是不可变的——方法返回新字符串而不是修改原始字符串。常用方法:Length、ToUpper/ToLower、Substring、IndexOf、Replace、Contains、Split。对于大量字符串操作,使用 StringBuilder 避免创建许多中间字符串。

csharp
string s = "Hello, World!";
Console.WriteLine(s.Length);        // 13
Console.WriteLine(s.ToUpper());     // HELLO, WORLD!
Console.WriteLine(s.Substring(0, 5)); // Hello
Console.WriteLine(s.IndexOf("World")); // 7
Console.WriteLine(s.Replace("World", "C#"));
Console.WriteLine(s.Contains("Hello")); // True
Console.WriteLine(s.Split(", "));   // ["Hello", "World!"]

StringBuilder

StringBuilder 是可变的,在循环中构建字符串很高效。字符串连接(+)每次创建新字符串,因此重复连接是 O(n^2)。StringBuilder 摊销为 O(n)。当有多次连接时使用 StringBuilder。

csharp
using System.Text;
var sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
    sb.Append("Line ").Append(i).Append("\n");
}
string result = sb.ToString();

// vs string concatenation (creates many temporary strings)
// string result = "";
// for (int i = 0; i < 100; i++) result += "Line " + i;

格式化与解析

string.Format 使用 {0}、{1} 占位符。格式说明符:F(固定)、N(带分隔符的数字)、P(百分比)、X(十六进制)。解析是区分区域设置的——小数分隔符因区域而异。使用 CultureInfo 进行显式控制,或 TryParse 确保安全。

csharp
// String formatting
string s1 = string.Format("{0} is {1} years old", "Alice", 30);
string s2 = $"{3.14159:F2}";  // "3.14"
string s3 = $"{1234567:N0}";  // "1,234,567" (thousands separator)

// Parsing
int n = int.Parse("42");
double d = double.Parse("3.14");
bool b = bool.Parse("true");

// Culture-aware parsing
double euro = double.Parse("3,14",
    System.Globalization.CultureInfo.GetCultureInfo("de-DE"));

字符串比较

使用 StringComparison.OrdinalIgnoreCase 进行不区分大小写的比较——比 ToLower() 然后 == 更清晰更快。避免对区域敏感的比较使用 ==;使用带 CultureInfo 的 string.Compare。对于字典键,使用 StringComparer.OrdinalIgnoreCase。

csharp
string a = "Hello", b = "hello";

// Case-sensitive
bool equal = a == b;  // false
bool equal2 = a.Equals(b);  // false

// Case-insensitive
bool equalCI = a.Equals(b, StringComparison.OrdinalIgnoreCase);  // true
int cmp = string.Compare(a, b, StringComparison.OrdinalIgnoreCase);

// Best practice for comparison
bool same = string.Equals(a, b,
    StringComparison.OrdinalIgnoreCase);

正则表达式

Regex(来自 System.Text.RegularExpressions)提供模式匹配。使用 @"" 逐字字符串,这样反斜杠不需要双重转义。对于重复使用,编译一次 Regex 实例并重用(RegexOptions.Compiled)以获得更好性能。

csharp
using System.Text.RegularExpressions;

string text = "Phone: 123-456-7890";
var match = Regex.Match(text, @"\d{3}-\d{3}-\d{4}");
if (match.Success) Console.WriteLine(match.Value);  // 123-456-7890

// Replace
string clean = Regex.Replace(text, @"\d", "X");
// "Phone: XXX-XXX-XXXX"

// Extract all matches
var emails = Regex.Matches("[email protected], [email protected]", @"\S+@\S+");
03

数字与数学

数值类型

C# 有固定大小类型:int(32 位)、long(64 位)、double(64 位浮点)、decimal(128 位,用于金融)。下划线(9_000_000)提高可读性。货币使用 decimal——它避免浮点舍入错误。后缀:L(long)、f(float)、m(decimal)。

csharp
int i = 42;
long big = 9_000_000_000L;  // underscores for readability
double d = 3.14159265;
float f = 3.14f;
decimal price = 19.99m;     // exact decimal for money
byte b = 255;

Console.WriteLine(sizeof(int));   // 4
Console.WriteLine(int.MaxValue);  // 2147483647

Math 类

Math 为常见操作提供静态方法。Math.Round 默认使用银行家舍入(舍入到偶数)——使用 MidpointRounding.AwayFromZero 进行学校舍入。Math.BigMul 处理 long 乘法以避免溢出。

csharp
double x = 2.5;
Math.Pow(x, 3);     // 15.625
Math.Sqrt(x);       // 1.581
Math.Abs(-5);       // 5
Math.Floor(3.7);    // 3
Math.Ceiling(3.2);  // 4
Math.Round(3.5);    // 4 (banker's rounding)
Math.Max(3, 7);     // 7
Math.Min(3, 7);     // 3
Math.PI;            // 3.14159...

随机数

Random 生成伪随机数。创建一个实例并重用它(在循环中创建许多实例可能由于基于时间的种子产生重复)。对于加密随机性,使用 System.Security.Cryptography.RandomNumberGenerator。C# 8+ 提供 Random.Shared 用于线程安全。

csharp
var random = new Random();
int r = random.Next(1, 101);     // 1-100
double d = random.NextDouble();   // 0.0-1.0
byte[] bytes = new byte[4];
random.NextBytes(bytes);

// C# 8+: thread-safe
// var rng = Random.Shared;
// int n = rng.Next(1, 101);

// Array of random items
int[] nums = Enumerable.Range(0, 5)
    .Select(_ => random.Next(1, 100)).ToArray();

类型转换

当不丢失数据时自动发生隐式转换(int 到 double)。可能丢失精度时需要显式转换(type)。Convert.ToInt32 处理许多类型并舍入(不像转换会截断)。字符串到数字转换始终优先使用 TryParse 以避免异常。

csharp
// Implicit (safe, no data loss)
int i = 42;
double d = i;  // int -> double

// Explicit cast (may lose data)
double pi = 3.14;
int truncated = (int)pi;  // 3

// Convert class
string s = "42";
int n = Convert.ToInt32(s);
string str = Convert.ToString(42);

// Parse / TryParse
int parsed = int.Parse("100");
bool ok = int.TryParse("abc", out int result);  // false

整数溢出与 checked

默认情况下,整数溢出静默回绕(unchecked)。checked 块在溢出时抛出 OverflowException——用于安全关键代码。对于真正大的数字,BigInteger(System.Numerics)处理任意精度整数而不会溢出。

csharp
int a = int.MaxValue;  // 2147483647
// a++;  // overflow: wraps to -2147483648 (unchecked by default)

checked {
    a++;  // throws OverflowException!
}

// BigInteger for arbitrary precision
using System.Numerics;
var big = BigInteger.Pow(2, 100);
Console.WriteLine(big);  // 1267650...376 (31 digits)
04

控制流

If / Else

if/else if/else 是标准的。三元运算符(condition ? a : b)是简洁的 if/else 表达式。C# 要求布尔条件——不像 C,整数不会隐式转换为 bool。即使单条语句也使用花括号以防止维护 bug。

csharp
int score = 85;
if (score >= 90) {
    Console.WriteLine("A");
} else if (score >= 80) {
    Console.WriteLine("B");
} else {
    Console.WriteLine("C");
}

// Ternary operator
string grade = score >= 60 ? "Pass" : "Fail";

Switch 与模式匹配

C# 8+ switch 表达式(=>)简洁且返回值。_ 模式是默认情况。模式匹配(is、switch)支持类型模式、属性模式和关系模式(> 18)。这比传统 switch 更强大且减少样板代码。

csharp
// Classic switch
int day = 3;
string name = day switch {
    1 => "Mon", 2 => "Tue", 3 => "Wed",
    6 or 7 => "Weekend",
    _ => "Invalid"  // default
};

// Pattern matching with types
object obj = "hello";
if (obj is string s && s.Length > 3) {
    Console.WriteLine(s);  // "hello"
}

// Property patterns
if (person is { Age: > 18, Name: "Alice" }) {
    // matches if Age > 18 and Name == "Alice"
}

循环

for 用于计数迭代;foreach 迭代集合(数组、列表、IEnumerable);while 循环直到条件为 false。foreach 是只读的——迭代期间不能修改集合。如果需要索引或需要修改元素,使用 for。

csharp
// for loop
for (int i = 0; i < 5; i++) {
    Console.WriteLine(i);
}

// foreach
string[] fruits = { "apple", "banana", "cherry" };
foreach (string fruit in fruits) {
    Console.WriteLine(fruit);
}

// while
int n = 5;
while (n > 0) Console.WriteLine(n--);

Break、Continue 与 goto

break 退出最近的循环或 switch;continue 跳到下一次迭代。C# 没有用于嵌套循环的带标签 break——使用标志、提取为带 return 的方法,或使用 LINQ。goto 在 switch 中用于 fall-through 是有效的,但其他方面不鼓励使用。

csharp
for (int i = 0; i < 10; i++) {
    if (i == 3) continue;  // skip 3
    if (i == 7) break;     // stop at 7
    Console.WriteLine(i);  // 0 1 2 4 5 6
}

// goto in switch (rare but valid)
switch (code) {
    case 1:
        DoSomething();
        goto case 2;  // fall through
    case 2:
        DoMore();
        break;
}

迭代器与 yield

yield return 将方法转换为惰性产生值的迭代器——一次一个按需产生。这对大型或无限序列内存高效。编译器生成状态机。yield break 提前结束迭代。LINQ 广泛使用迭代器。

csharp
// yield return creates an iterator (lazy evaluation)
IEnumerable<int> GetNumbers() {
    for (int i = 0; i < 5; i++) {
        yield return i;
    }
}

foreach (int n in GetNumbers()) {
    Console.WriteLine(n);  // 0 1 2 3 4
}

// Infinite sequence (consumed lazily)
IEnumerable<int> Naturals() {
    int n = 0;
    while (true) yield return n++;
}
05

方法与委托

方法定义

方法可以是表达式体(=>)用于单行。默认参数使参数可选。命名参数(name:)提高多参数调用的可读性并允许跳过可选参数。C# 不支持仅返回类型不同的方法重载。

csharp
static int Add(int a, int b) {
    return a + b;
}

// Expression-bodied (C# 6+)
static int Multiply(int a, int b) => a * b;

// Default parameters
static void Greet(string name = "Guest", int times = 1) {
    for (int i = 0; i < times; i++)
        Console.WriteLine($"Hi {name}!");
}

Greet();              // Hi Guest!
Greet("Alice", 3);    // Hi Alice! x3
Greet(times: 2);      // named argument

out、ref 与 params

out 参数必须由方法赋值(调用者不需要初始化)。ref 参数必须由调用者初始化且可以被修改。params 允许可变数量的参数。C# 7+ 允许内联 out var 声明:TryParse(s, out var result)。

csharp
// out: must be assigned in method (caller doesn't init)
bool TryParse(string s, out int result) {
    result = 0;
    return int.TryParse(s, out result);
}

// ref: caller must initialize
void Increment(ref int n) { n++; }
int x = 5;
Increment(ref x);  // x is now 6

// params: variable arguments
int Sum(params int[] nums) => nums.Sum();
Sum(1, 2, 3);  // 6
Sum();         // 0

委托与事件

委托是类型安全的函数指针。Func<T,TResult> 接受输入并返回值;Action<T> 返回 void;Predicate<T> 返回 bool。事件是带 +=/-= 订阅和 ?.Invoke 安全引发(null 检查)的委托。事件用于观察者模式(发布/订阅)。

csharp
// Delegate type
delegate int MathOp(int a, int b);

MathOp add = (a, b) => a + b;
MathOp mul = (a, b) => a * b;
Console.WriteLine(add(3, 4));  // 7

// Built-in delegate types
Func<int, int, int> divide = (a, b) => a / b;
Action<string> log = msg => Console.WriteLine(msg);
Predicate<int> isEven = n => n % 2 == 0;

// Events
event Action<string> OnMessage;
OnMessage += msg => Console.WriteLine($"Got: {msg}");
OnMessage?.Invoke("Hello");

Lambda 表达式

Lambda 是使用 => 的匿名方法。它们从封闭作用域捕获变量(闭包)。捕获的变量在 lambda 执行时求值,而不是创建时——在循环中注意这一点。在 LINQ、事件和回调中广泛使用 lambda。

csharp
// Lambda with explicit types
Func<int, int> square = (int x) => x * x;

// Type inference
Func<int, int, int> add = (a, b) => a + b;

// Statement lambda
Action<string> log = msg => {
    Console.WriteLine($"[{DateTime.Now}] {msg}");
};

// Capturing variables (closures)
int factor = 3;
Func<int, int> multiply = x => x * factor;
Console.WriteLine(multiply(5));  // 15

扩展方法

扩展方法在不修改现有类型的情况下向其添加方法。第一个参数前的 this 关键字将其标记为扩展方法。它们必须在静态类中。LINQ 完全实现为 IEnumerable 上的扩展方法。这是添加实用方法的强大方式。

csharp
static class StringExtensions {
    public static int WordCount(this string s) {
        return s.Split(new[] { ' ' },
            StringSplitOptions.RemoveEmptyEntries).Length;
    }
}

string text = "Hello World C#";
Console.WriteLine(text.WordCount());  // 3

// LINQ is built on extension methods
var evens = nums.Where(n => n % 2 == 0);
06

集合

List<T>

List<T> 是动态数组(类似 C++ 的 vector 或 Java 的 ArrayList)。Add/Remove 是摊销 O(1) / O(n)。Count 给出元素数量(不是容量)。Find/FindAll 使用谓词。对于频繁的中间插入/删除,LinkedList<T> 可能更好。

csharp
using System.Collections.Generic;
var nums = new List<int> { 1, 2, 3 };
nums.Add(4);
nums.AddRange(new[] { 5, 6 });
nums.Remove(3);
nums.RemoveAt(0);
Console.WriteLine(nums.Count);  // 4
Console.WriteLine(nums.Contains(4));  // true

// Find
int first = nums.Find(n => n > 3);
List<int> all = nums.FindAll(n => n > 3);

Dictionary<TKey, TValue>

Dictionary 是哈希表,平均 O(1) 查找。TryGetValue 比索引器更安全(后者抛出 KeyNotFoundException)。对于不区分大小写的字符串键,向构造函数传递 StringComparer.OrdinalIgnoreCase。Dictionary 不保留插入顺序(需要时使用 OrderedDictionary)。

csharp
var ages = new Dictionary<string, int> {
    ["Alice"] = 30,
    ["Bob"] = 25
};
ages["Charlie"] = 35;

// Safe access
if (ages.TryGetValue("Alice", out int age)) {
    Console.WriteLine($"Alice: {age}");
}

// Iterate
foreach (var kv in ages) {
    Console.WriteLine($"{kv.Key}: {kv.Value}");
}

// Case-insensitive keys
var dict = new Dictionary<string, int>(
    StringComparer.OrdinalIgnoreCase);

HashSet<T> 与 SortedSet<T>

HashSet<T> 存储唯一元素,O(1) 查找——用于去重和成员测试。它支持集合操作(Union、Intersect、Except)。SortedSet<T>(红黑树)保持元素有序,O(log n) 操作。顺序不重要时使用 HashSet,重要时使用 SortedSet。

csharp
var set = new HashSet<int> { 1, 2, 3 };
set.Add(2);  // duplicate, ignored
set.Add(4);
Console.WriteLine(set.Count);  // 4
Console.WriteLine(set.Contains(3));  // true

// Set operations
var other = new HashSet<int> { 3, 4, 5 };
set.IntersectWith(other);  // set: {3, 4}
set.UnionWith(other);      // set: {3, 4, 5}

// SortedSet keeps elements sorted
var sorted = new SortedSet<int> { 3, 1, 2 };  // {1, 2, 3}

Queue<T> 与 Stack<T>

Queue<T> 是 FIFO(Enqueue/Dequeue)——用于任务调度、BFS。Stack<T> 是 LIFO(Push/Pop)——用于撤销/重做、DFS、表达式求值。两者的核心操作都是 O(1)。对于并发场景,使用 System.Collections.Concurrent 的 ConcurrentQueue 和 ConcurrentStack。

csharp
// Queue: FIFO (first in, first out)
var queue = new Queue<string>();
queue.Enqueue("first");
queue.Enqueue("second");
string next = queue.Dequeue();  // "first"
string peek = queue.Peek();     // "second"

// Stack: LIFO (last in, first out)
var stack = new Stack<int>();
stack.Push(1);
stack.Push(2);
int top = stack.Pop();   // 2
int peek = stack.Peek(); // 1

数组

数组是固定大小的。Array.Sort 就地排序。多维数组([,])是矩形的(统一列)。交错数组([][])是数组的数组(行可以有不同长度)。动态调整大小使用 List<T>;固定大小、性能关键的数据使用数组。

csharp
int[] nums = { 1, 2, 3, 4, 5 };
Console.WriteLine(nums.Length);  // 5
Array.Sort(nums);
Array.Reverse(nums);
int found = Array.IndexOf(nums, 3);
int[] copy = new int[5];
Array.Copy(nums, copy, 5);

// Multidimensional
int[,] grid = { { 1, 2 }, { 3, 4 } };
Console.WriteLine(grid[1, 0]);  // 3

// Jagged array (array of arrays)
int[][] jagged = { new[] { 1, 2 }, new[] { 3, 4, 5 } };
07

类与面向对象

类与属性

自动属性({ get; set; })自动生成支持字段。表达式体属性(=>)在访问时计算。C# 9+ 仅 init 属性({ get; init; })仅在构造期间可设置,实现不可变性。对象初始化器:new Person { Name = "X" }。

csharp
class Person {
    // Auto-properties (C# 3+)
    public string Name { get; set; }
    public int Age { get; set; }

    // Read-only property
    public bool IsAdult => Age >= 18;

    // Constructor
    public Person(string name, int age) {
        Name = name;
        Age = age;
    }
}

var p = new Person("Alice", 30);
Console.WriteLine(p.IsAdult);  // true

继承与 virtual

virtual 标记方法可重写;子类中的 override 提供新实现。C# 要求显式 override(不像 Java)。sealed 防止类被继承或方法被重写。所有类隐式继承自 object(System.Object)。

csharp
class Animal {
    public virtual void Speak() {
        Console.WriteLine("...");
    }
}

class Dog : Animal {
    public override void Speak() {
        Console.WriteLine("Woof");
    }
}

Animal a = new Dog();
a.Speak();  // Woof (polymorphism)

// sealed prevents further inheritance
sealed class Puppy : Dog { }

接口

接口定义没有实现的契约。一个类可以实现多个接口(不像单类继承)。C# 8+ 允许默认接口方法。接口用于多态和依赖注入。命名约定:前缀 I(IShape、ILogger)。

csharp
interface IShape {
    double Area();  // interface member
    double Perimeter { get; }
}

class Circle : IShape {
    public double Radius { get; set; }
    public double Area() => Math.PI * Radius * Radius;
    public double Perimeter => 2 * Math.PI * Radius;
}

// Default interface methods (C# 8+)
interface ILogger {
    void Log(string msg);
    void Error(string msg) => Log($"ERROR: {msg}");  // default
}

抽象类

抽象类不能被实例化,可以同时有抽象(必须重写)和具体(virtual)成员。有共享实现时使用抽象类;纯契约使用接口。一个类只能继承一个抽象类但可以实现多个接口。

csharp
abstract class Shape {
    public abstract double Area();  // must be overridden
    public virtual void Describe() {
        Console.WriteLine($"Area: {Area()}");
    }
}

class Rectangle : Shape {
    public double Width { get; set; }
    public double Height { get; set; }
    public override double Area() => Width * Height;
}

// Cannot instantiate abstract class
// var s = new Shape();  // ERROR
var r = new Rectangle { Width = 3, Height = 4 };

Record 与 Struct

Record(C# 9+)提供基于值的相等性、不可变性和简洁语法——非常适合 DTO 和数据模型。with 表达式创建修改属性的副本。Struct 是值类型(赋值时拷贝,栈分配)——用于小型轻量数据。类是引用类型(堆分配)。

csharp
// Record (C# 9+): value-based equality, immutability
record Point(int X, int Y);

var p1 = new Point(3, 4);
var p2 = new Point(3, 4);
Console.WriteLine(p1 == p2);  // true (value equality)

// with expression (non-destructive mutation)
var p3 = p1 with { X = 10 };

// Struct: value type (stack-allocated)
struct Vector {
    public double X, Y;
    public Vector(double x, double y) { X = x; Y = y; }
}
08

LINQ

Where 与 Select

LINQ(语言集成查询)对集合提供类 SQL 查询。Where 过滤,Select 转换(映射)。LINQ 是惰性的——查询仅在枚举时执行(例如通过 ToList())。这使高效链式操作无需中间集合成为可能。方法语法(上)最常见;查询语法也可用。

csharp
using System.Linq;
int[] nums = { 1, 2, 3, 4, 5, 6 };

// Filter
var evens = nums.Where(n => n % 2 == 0);  // 2, 4, 6

// Map/Transform
var squares = nums.Select(n => n * n);  // 1, 4, 9, 16, 25, 36

// Chain operations
var result = nums
    .Where(n => n > 2)
    .Select(n => n * 10)
    .ToList();  // 30, 40, 50, 60

排序与分组

OrderBy/OrderByDescending 排序;ThenBy 添加次要排序条件。GroupBy 按键聚集元素,返回 IGrouping<key, element> 组。每个组有一个 Key 且本身是其成员的 IEnumerable。这取代了分组的手动循环和字典。

csharp
var people = new[] {
    new { Name = "Alice", Age = 30 },
    new { Name = "Bob", Age = 25 },
    new { Name = "Charlie", Age = 30 },
};

// Order
var sorted = people.OrderBy(p => p.Age).ThenBy(p => p.Name);
var desc = people.OrderByDescending(p => p.Age);

// Group
var grouped = people.GroupBy(p => p.Age);
foreach (var g in grouped) {
    Console.WriteLine($"Age {g.Key}: {g.Count()} people");
}
// Age 25: 1 people
// Age 30: 2 people

聚合

LINQ 聚合方法(Sum、Average、Min、Max、Count)从集合计算单个值。Aggregate 最通用——它是一个 fold/reduce,累积应用函数。这些在空序列上抛出异常;使用 *OrDefault 变体或先检查是否为空。

csharp
int[] nums = { 1, 2, 3, 4, 5 };

Console.WriteLine(nums.Sum());    // 15
Console.WriteLine(nums.Average()); // 3
Console.WriteLine(nums.Min());    // 1
Console.WriteLine(nums.Max());    // 5
Console.WriteLine(nums.Count());  // 5

// Count with predicate
int evens = nums.Count(n => n % 2 == 0);  // 2

// Aggregate (fold/reduce)
int product = nums.Aggregate((a, b) => a * b);  // 120
string joined = nums.Aggregate("", (s, n) => s + n);  // "12345"

First、Single 与 ElementAt

First 返回第一个元素(空时抛出异常);FirstOrDefault 未找到时返回默认值(int 为 0,引用类型为 null)。Single 要求恰好一个元素(否则抛出异常)——用于验证。Any/All 返回 bool 而不枚举整个集合(短路)。

csharp
int[] nums = { 1, 2, 3, 4, 5 };

int first = nums.First();           // 1
int firstEven = nums.First(n => n % 2 == 0);  // 2
int firstOrDef = nums.FirstOrDefault(n => n > 10);  // 0 (default)

int single = new[] { 42 }.Single();  // 42
// nums.Single();  // throws! more than one element

int third = nums.ElementAt(2);  // 3 (0-indexed)

bool any = nums.Any(n => n > 3);  // true
bool all = nums.All(n => n > 0);  // true

Join 与 Zip

Join 执行内连接(类似 SQL)按键匹配元素。Zip 按位置配对两个序列的元素。两者都产生新序列。LINQ 还支持 GroupJoin(带分组的左连接)。这些对于组合来自多个源的相关数据很强大。

csharp
var users = new[] {
    new { Id = 1, Name = "Alice" },
    new { Id = 2, Name = "Bob" },
};
var orders = new[] {
    new { UserId = 1, Item = "Book" },
    new { Id = 2, Item = "Pen" },
};

// Join (inner join)
var joined = users.Join(orders,
    u => u.Id, o => o.UserId,
    (u, o) => new { u.Name, o.Item });
// { Alice, Book }

// Zip (pair elements by position)
var names = new[] { "Alice", "Bob" };
var ages = new[] { 30, 25 };
var pairs = names.Zip(ages, (n, a) => $"{n}: {a}");
// "Alice: 30", "Bob: 25"
09

错误处理

Try / Catch / Finally

先捕获特定异常,最后捕获一般 Exception(最具体到最不具体)。finally 始终运行——用于清理(关闭文件、释放资源)。避免 catch (Exception) 用于控制流;只捕获你能处理的。使用 throw;(不是 throw e;)保留栈跟踪。

csharp
try {
    int x = 10;
    int y = 0;
    int z = x / y;  // throws DivideByZeroException
} catch (DivideByZeroException e) {
    Console.WriteLine($"Error: {e.Message}");
} catch (Exception e) {
    Console.WriteLine($"Unexpected: {e.Message}");
} finally {
    // always runs, even with return/throw
    Console.WriteLine("Cleanup");
}

自定义异常

从 Exception(或更具体的基类)派生自定义异常。添加有助于调试的上下文字段。始终用消息调用基类构造函数。按约定,异常类名以 Exception 结尾。自定义异常用于调用者可以处理的领域特定错误条件。

csharp
class ValidationError : Exception {
    public string Field { get; }
    public ValidationError(string field, string msg)
        : base(msg) {
        Field = field;
    }
}

throw new ValidationError("email", "Invalid format");

try {
    // ...
} catch (ValidationError e) {
    Console.WriteLine($"{e.Field}: {e.Message}");
}

using 与 IDisposable

using 确保即使发生异常也调用 Dispose()——这是 C# 用于资源管理的 RAII 等价物。using 声明(C# 8+)更清晰——Dispose 在作用域结束时调用。为持有非托管资源(文件句柄、数据库连接)的类实现 IDisposable。

csharp
// using statement (C# 1+)
using (var file = new StreamReader("data.txt")) {
    string content = file.ReadToEnd();
}  // file.Dispose() called automatically

// using declaration (C# 8+)
using var file2 = new StreamReader("data.txt");
string content2 = file2.ReadToEnd();
// Dispose called at end of scope

// Implementing IDisposable
class Resource : IDisposable {
    public void Dispose() {
        // release unmanaged resources
        GC.SuppressFinalize(this);
    }
}

Null 处理

C# 8+ 可空引用类型(string?)启用编译时 null 安全。?. 返回 null 而不是抛出 NullReferenceException。?? 提供回退。throw 表达式(?? throw)用于验证很简洁。在 .csproj 中启用 <Nullable>enable</Nullable> 进行项目范围的 null 检查。

csharp
string? name = null;

// Null-conditional operator
int? length = name?.Length;  // null (no exception)

// Null-coalescing operator
string display = name ?? "Unknown";  // "Unknown"

// Throw if null
string required = name ?? throw new ArgumentNullException(nameof(name));

// Pattern matching null check
if (name is null) { /* ... */ }
if (name is { Length: > 5 }) { /* not null and length > 5 */ }

异常过滤器

异常过滤器(when)向 catch 块添加条件——仅当过滤器为 true 时才捕获异常。这比捕获并重新抛出更强大,因为它保留栈跟踪。日志模式(when 返回 false)让你观察异常而不处理它们。

csharp
try {
    // ...
} catch (HttpRequestException e) when (e.StatusCode == 404) {
    Console.WriteLine("Not found");
} catch (HttpRequestException e) when (e.StatusCode >= 500) {
    Console.WriteLine("Server error");
} catch (HttpRequestException e) {
    Console.WriteLine($"HTTP error: {e.Message}");
}

// Log without catching
catch (Exception e) when (LogError(e)) {
    // never enters if LogError returns false
}
private bool LogError(Exception e) { /* log */ return false; }
10

文件 I/O 与异步

File 类(快速 I/O)

File 为快速一次性文件操作提供静态方法——简单但将整个文件加载到内存。对于大文件,使用 StreamReader/StreamWriter 逐行处理。File.Exists 检查存在但有 TOCTOU 竞争——始终处理文件操作的异常。

csharp
using System.IO;

// Read all text at once
string content = File.ReadAllText("input.txt");
string[] lines = File.ReadAllLines("input.txt");

// Write
File.WriteAllText("output.txt", "Hello\n");
File.WriteAllLines("output.txt", new[] { "Line 1", "Line 2" });

// Append
File.AppendAllText("log.txt", $"[{DateTime.Now}] Event\n");

// File info
bool exists = File.Exists("data.txt");
var info = new FileInfo("data.txt");
Console.WriteLine(info.Length);  // bytes

流(StreamReader/Writer)

StreamReader/StreamWriter 逐行处理文件而不将所有内容加载到内存——用于大文件。using 语句确保即使发生异常文件也会关闭。StreamWriter 缓冲数据;调用 Flush() 立即写入,或让 Dispose 处理。

csharp
using System.IO;

// Reading line by line
using var reader = new StreamReader("input.txt");
string? line;
while ((line = reader.ReadLine()) != null) {
    Console.WriteLine(line);
}

// Writing
using var writer = new StreamWriter("output.txt");
writer.WriteLine("First line");
writer.WriteLine("Second line");
writer.Flush();  // optional, using disposes and flushes

async / await

async/await 启用非阻塞异步操作。方法返回 Task<T>(或 void 返回 Task)。await 挂起方法而不阻塞线程——线程被释放用于其他工作。这对 UI 和 Web 应用中的 I/O 密集型操作(文件、网络、数据库)至关重要。

csharp
async Task<string> ReadFileAsync(string path) {
    using var reader = new StreamReader(path);
    return await reader.ReadToEndAsync();
}

// Calling async method
string content = await ReadFileAsync("data.txt");
Console.WriteLine(content.Length);

// Async doesn't block the calling thread
// The thread is freed while waiting for I/O

Task 与 Parallel

Task.Run 在线程池上调度工作。Task.WhenAll 并发等待多个任务(CPU 密集型工作不要使用 async/await——使用 Task.Run)。Parallel.For 用于 CPU 密集型并行循环。PLINQ(AsParallel)并行化 LINQ 查询。I/O 使用 async,CPU 使用 Parallel/PLINQ。

csharp
// Run work on a thread pool thread
var task = Task.Run(() => HeavyComputation());
int result = await task;

// Run multiple tasks concurrently
var t1 = Task.Run(() => DownloadAsync("url1"));
var t2 = Task.Run(() => DownloadAsync("url2"));
await Task.WhenAll(t1, t2);  // wait for both

// Parallel.For for CPU-bound parallelism
Parallel.For(0, 100, i => {
    Process(i);  // runs on multiple threads
});

// PLINQ
var results = nums.AsParallel()
    .Where(n => IsPrime(n))
    .ToArray();

JSON 序列化

System.Text.Json 是现代高性能 JSON 序列化器(大多数情况下取代 Newtonsoft.Json)。Serialize/Deserialize 处理对象<->JSON。使用 JsonSerializerOptions 进行格式化(缩进、camelCase)。对于异步,使用基于流的方法避免将大型 JSON 加载到内存。

csharp
using System.Text.Json;

var person = new { Name = "Alice", Age = 30 };

// Serialize
string json = JsonSerializer.Serialize(person);
// {"Name":"Alice","Age":30}

// Deserialize
Person p = JsonSerializer.Deserialize<Person>(json);

// With options
var options = new JsonSerializerOptions {
    WriteIndented = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string pretty = JsonSerializer.Serialize(person, options);

// Async (for streams)
using var stream = File.OpenRead("data.json");
var data = await JsonSerializer.DeserializeAsync<Person>(stream);
11

LINQ 深入

查询语法与方法语法

LINQ 有两种等价语法。查询语法类似 SQL,对于复杂连接/分组更可读。方法语法(流式)更常见,支持所有运算符,自然链式。它们编译为相同的 IL。复杂多子句查询使用查询语法;简单链使用方法语法。两者都是惰性求值的(延迟执行)。

csharp
List<int> nums = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Query syntax (SQL-like)
var evens = from n in nums
            where n % 2 == 0
            orderby n descending
            select n * 2;

// Method syntax (fluent) — equivalent
var evens2 = nums.Where(n => n % 2 == 0)
                  .OrderByDescending(n => n)
                  .Select(n => n * 2);

// Query syntax supports: from, where, orderby, select, group, join, let
// Method syntax supports everything query does, plus more

延迟执行与立即执行

LINQ 使用延迟执行——Where/Select/OrderBy 只构建查询;枚举时才运行。这意味着结果反映枚举时的源状态,而不是查询创建时。ToList/ToArray/Count 等强制立即执行,捕获快照。注意:枚举延迟查询两次会运行两次。需要稳定结果时用 ToList 缓存。

csharp
var nums = new List<int> { 1, 2, 3 };

// Deferred: query not run until enumerated
var query = nums.Where(n => n > 1);
nums.Add(4);  // query sees this!
Console.WriteLine(query.Count());  // 3 (2, 3, 4)

// Immediate: forces execution now
var list = nums.Where(n => n > 1).ToList();  // runs now
nums.Add(5);
Console.WriteLine(list.Count);  // still 3 (snapshot)

// Operators that force immediate execution:
// ToList, ToArray, ToDictionary, ToLookup,
// Count, Sum, Min, Max, Average, First, Last, Any, All

分组与连接

GroupBy 按键聚集元素,返回 IGrouping<key, element> 序列。Join 执行内连接(匹配键)。分组连接(join...into)创建分层结果并通过 DefaultIfEmpty() 实现左外连接。这些操作对数据分析很强大。对于大数据集,考虑使用 ToLookup 进行重复查找(它是预计算的分组)。

csharp
var students = new[] {
    new { Name = "Alice", Grade = "A", Dept = "CS" },
    new { Name = "Bob", Grade = "B", Dept = "Math" },
    new { Name = "Carol", Grade = "A", Dept = "CS" },
};

// Group by department
var byDept = students.GroupBy(s => s.Dept);
foreach (var g in byDept) {
    Console.WriteLine(`{g.Key}: {g.Count()}`);
    // CS: 2, Math: 1
}

// Inner join
var courses = new[] { new { Dept = "CS", Course = "Algo" } };
var joined = from s in students
             join c in courses on s.Dept equals c.Dept
             select new { s.Name, c.Course };

// Group join (left outer join with DefaultIfEmpty)
var groupJoin = from s in students
                join c in courses on s.Dept equals c.Dept into sc
                from c in sc.DefaultIfEmpty()
                select new { s.Name, Course = c?.Course };

聚合与量词

LINQ 提供标准聚合(Sum、Min、Max、Average、Count)和用于自定义归约的通用 Aggregate(类似 JavaScript reduce)。量词(Any、All、Contains)返回布尔值并短路——Any 在第一个匹配时停止,All 在第一个不匹配时停止。使用 Any()(不是 Count() > 0)检查存在——更高效可读。

csharp
var nums = new[] { 1, 2, 3, 4, 5 };

// Aggregations
int sum = nums.Sum();              // 15
double avg = nums.Average();       // 3.0
int min = nums.Min();              // 1
int max = nums.Max();              // 5
int count = nums.Count();          // 5

// Custom aggregation (like reduce/fold)
int product = nums.Aggregate((a, b) => a * b);  // 120
int withSeed = nums.Aggregate(10, (a, b) => a + b);  // 25

// Quantifiers (return bool)
bool anyEven = nums.Any(n => n % 2 == 0);  // true
bool allPos = nums.All(n => n > 0);        // true
bool contains3 = nums.Contains(3);         // true

// LongCount for > int.MaxValue elements
long big = largeList.LongCount();

IEnumerable 与 IQueryable

IEnumerable<T> 用于内存中集合(LINQ to Objects)——使用委托,本地执行。IQueryable<T> 用于远程源(Entity Framework、LINQ to SQL)——使用表达式树,转换为源的查询语言(SQL)。IQueryable 高效组合服务器端查询。当需要无法转换的客户端逻辑时,用 AsEnumerable() 切换到 IEnumerable。

csharp
// IEnumerable<T>: in-memory, LINQ to Objects
List<int> nums = new() { 1, 2, 3 };
var q1 = nums.Where(n => n > 1);  // executes in memory

// IQueryable<T>: translates to query language (SQL)
// Expression trees, not delegates
using var db = new MyDbContext();
var q2 = db.Users.Where(u => u.Age > 18);  // generates SQL
// SELECT * FROM Users WHERE Age > 18

// IQueryable composes queries that execute server-side
var query = db.Users
    .Where(u => u.Active)
    .OrderBy(u => u.Name)
    .Take(10);
// All translated to one SQL query

// Force client-side with AsEnumerable()
var clientSide = db.Users.AsEnumerable()
    .Where(u => ExpensiveCheck(u));
12

Async/Await 深入

Task 与 Task<T>

Task 表示没有结果的异步操作;Task<T> 返回 T。ValueTask<T>(C# 7)在结果通常同步可用时(缓存场景)避免堆分配——但只能 await 一次。异步 API 中同步结果使用 Task.FromResult。异步方法优先使用 Task 而非 void(void 仅用于事件处理程序)。

csharp
// Task: represents an async operation (no result)
async Task DoWorkAsync() {
    await Task.Delay(1000);
    Console.WriteLine("Done");
}

// Task<T>: async operation returning T
async Task<int> GetCountAsync() {
    await Task.Delay(500);
    return 42;
}

// ValueTask<T>: lightweight, avoids allocation
ValueTask<int> GetCachedAsync() {
    if (_cache != null) return new ValueTask<int>(_cache);
    return new ValueTask<int>(LoadFromDbAsync());
}

// Converting to Task
Task t = Task.Run(() => Console.WriteLine("hi"));
Task<int> t2 = Task.FromResult(42);  // already complete
Task completed = Task.CompletedTask;

CancellationToken

CancellationToken 启用协作式取消。将其传递给异步方法并在循环中检查 ThrowIfCancellationRequested()。调用者通过 CancellationTokenSource.Cancel() 取消;方法决定何时/如何响应。异步 API 中始终接受 CancellationToken 参数(特别是库代码)。超时使用带 TimeSpan 的 CancellationTokenSource。取消抛出 OperationCanceledException。

csharp
async Task DownloadAsync(string url, CancellationToken ct) {
    using var client = new HttpClient();
    // Pass token to async operations
    var data = await client.GetByteArrayAsync(url, ct);
    // Check manually in loops
    foreach (var chunk in data) {
        ct.ThrowIfCancellationRequested();
        Process(chunk);
    }
}

// Usage with timeout
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try {
    await DownloadAsync("https://example.com", cts.Token);
} catch (OperationCanceledException) {
    Console.WriteLine("Timed out or cancelled");
}

// Cooperative cancellation: caller cancels, method checks
cts.Cancel();  // request cancellation

Task.WhenAll 与 WhenAny

Task.WhenAll 并发等待多个任务并在全部完成时继续(并行)。Task.WhenAny 在第一个任务完成时继续(竞争/冗余)。WhenAll 默认仅抛出第一个异常;检查每个任务的 IsFaulted/Exception 查看所有失败。对于即发即忘,使用 Task.Run 并在内部处理异常以避免未观察到的异常。

csharp
// WhenAll: wait for ALL to complete (like Promise.all)
var tasks = new[] {
    FetchAsync("url1"),
    FetchAsync("url2"),
    FetchAsync("url3"),
};
string[] results = await Task.WhenAll(tasks);
// Continues when all 3 done; throws AggregateException if any fails

// WhenAny: wait for FIRST to complete (race)
var racing = new[] { PrimaryAsync(), FallbackAsync() };
Task<string> firstDone = await Task.WhenAny(racing);
string result = await firstDone;

// WhenAll with exception handling
try {
    await Task.WhenAll(tasks);
} catch (Exception ex) {
    // Only first exception is thrown; access all via Task.Exception
    foreach (var inner in tasks.Where(t => t.IsFaulted))
        Console.WriteLine(inner.Exception);
}

异步流(IAsyncEnumerable)

IAsyncEnumerable<T>(C# 8)是异步流——在项可用时产生它们,yield 之间有 await。用 await foreach 消费。非常适合流式传输大型数据集或实时数据而无需将所有内容缓冲到内存。[EnumeratorCancellation] 属性确保使用 WithCancellation 消费流时取消令牌正确流动。

csharp
// C# 8: async iterator (like async generator)
async IAsyncEnumerable<int> GenerateAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    for (int i = 0; i < 10; i++) {
        await Task.Delay(100, ct);
        yield return i;  // async yield
    }
}

// Consume with await foreach
await foreach (int n in GenerateAsync().WithCancellation(ct)) {
    Console.WriteLine(n);
}

// Use case: streaming data from a database or API
async IAsyncEnumerable<Record> StreamRecordsAsync() {
    await using var reader = await cmd.ExecuteReaderAsync();
    while (await reader.ReadAsync())
        yield return MapRecord(reader);
}

异步陷阱(死锁)

头号异步陷阱:当存在 SynchronizationContext 时(UI 应用、旧版 ASP.NET),对 Task 调用 .Result 或 .Wait() 可能死锁。await 捕获上下文;.Result 阻塞续体需要的线程。修复:使方法一路 async 到顶,或在库代码中使用 ConfigureAwait(false)。async void 危险——异常无法捕获且不可 await。改用 async Task。

csharp
// BAD: .Result blocks (can deadlock in UI/ASP.NET classic)
public string BadMethod() {
    string data = GetDataAsync().Result;  // DEADLOCK risk!
    return data;
}

// GOOD: async all the way
public async Task<string> GoodMethodAsync() {
    string data = await GetDataAsync();
    return data;
}

// Why deadlock: in UI/ASP.NET context, await captures
// SynchronizationContext. .Result blocks the thread,
// but the continuation needs that same thread → deadlock.

// Fix if you MUST block (last resort):
string data = Task.Run(GetDataAsync).Result;  // offloads
// Or ConfigureAwait(false) in library code:
await GetDataAsync().ConfigureAwait(false);

// async void is EVIL (except event handlers)
async void Bad() { await Task.Delay(1); throw new Exception(); }
// Exception can't be caught by caller!
13

事件与委托

委托基础

委托是类型安全的函数指针。自定义委托(delegate int MathOp(int,int))很大程度上被内置 Action(void 返回)和 Func<T>(返回 T)取代。委托是多播的——它们可以持有多个方法(用 + 组合)。调用时所有方法运行。多播委托的返回值是最后一个方法的结果。大多数现代代码使用 Action/Func。

csharp
// Declare a delegate type
public delegate int MathOp(int a, int b);

// Methods matching the signature
static int Add(int a, int b) => a + b;
static int Mul(int a, int b) => a * b;

MathOp op = Add;
Console.WriteLine(op(2, 3));  // 5
op = Mul;
Console.WriteLine(op(2, 3));  // 6

// Multicast: combine delegates
MathOp multi = (MathOp)Add + Mul;
// Note: returns last result (Add's), but both run
// (return value of multicast is the last one's)

// Built-in: Action (void), Func<T> (returns T)
Action<string> log = s => Console.WriteLine(s);
Func<int, int, int> add = (a, b) => a + b;
Predicate<int> isEven = n => n % 2 == 0;  // returns bool

事件(发布者/订阅者)

事件是委托的受限形式——只有声明类可以调用它们,但外部代码可以订阅/取消订阅(+=/-=)。使用 EventHandler<TArgs> 实现标准模式。始终使用 null 条件运算符(Click?.Invoke),因为没有订阅者的事件为 null。始终取消订阅以防止内存泄漏(事件持有处理程序的强引用)。

csharp
public class Button {
    // Event: restricted multicast delegate
    public event EventHandler<EventArgs> Click;

    public void SimulateClick() {
        // Null-conditional invoke pattern (thread-safe)
        Click?.Invoke(this, EventArgs.Empty);
    }
}

// Custom event args
public class SearchEventArgs : EventArgs {
    public string Query { get; init; }
}

// Subscribe
var btn = new Button();
btn.Click += (sender, e) => Console.WriteLine("Clicked!");
btn.Click += HandlerMethod;  // method group

// Unsubscribe (MUST to avoid memory leaks)
btn.Click -= HandlerMethod;

void HandlerMethod(object sender, EventArgs e) { /* ... */ }

INotifyPropertyChanged(数据绑定)

INotifyPropertyChanged 是 WPF、MAUI 和 WinForms 中数据绑定的标准模式。属性更改时,引发 PropertyChanged 以便绑定的 UI 更新。[CallerMemberName](C# 5)自动传递调用属性的名称——无魔术字符串。检查(if != value)防止不必要的通知。ViewModel 大量使用此模式;源生成器(CommunityToolkit.Mvvm)可自动生成此样板。

csharp
using System.ComponentModel;
using System.Runtime.CompilerServices;

public class ViewModel : INotifyPropertyChanged {
    private string _name;
    public string Name {
        get => _name;
        set {
            if (_name != value) {
                _name = value;
                OnPropertyChanged();  // raises event
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    // CallerMemberName auto-fills the property name
    protected void OnPropertyChanged([CallerMemberName] string prop = null) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
    }
}

// WPF/MAUI bindings automatically update UI when Name changes

事件与委托

关键区别:公共委托字段可以被任何人调用、重新分配或清除。事件将外部访问限制为仅 +=(订阅)和 -=(取消订阅)——只有声明类可以调用或清除它。这种封装是事件成为观察者模式标准的原因。事件还生成线程安全的 add/remove 访问器并与设计器工具集成。

csharp
// Public delegate: anyone can invoke or clear
public delegate void Notify();
public class Bad {
    public Notify OnDone;  // field-like, public
}
// External code can do:
//   bad.OnDone();        // invoke (BAD: not the owner)
//   bad.OnDone = null;   // clear all subscribers (BAD)

// Event: only owner can invoke/clear
public class Good {
    public event Notify OnDone;  // restricted
    public void Complete() {
        OnDone?.Invoke();  // OK: owner invokes
    }
}
// External code can ONLY += or -= (subscribe/unsubscribe)
// Cannot invoke or clear from outside

// Event = delegate + access restriction
// Under the hood: event generates add/remove accessors

弱事件(防止内存泄漏)

事件持有订阅者的强引用,如果订阅者应被垃圾回收但发布者存活则导致内存泄漏。解决方案:显式取消订阅(最佳)、使用 WeakEventManager(WPF),或用 WeakReference 实现弱事件模式。这是长时间运行应用(服务器、桌面应用)中常见的泄漏源。始终在 Dispose 中或订阅者生命周期结束时取消订阅。

csharp
// Problem: event holds strong ref to subscriber → leak
// if subscriber should be GC'd but publisher lives long

// Solution 1: WeakEventManager (WPF)
WeakEventManager<Publisher, EventArgs>.AddHandler(pub, "Event", Handler);

// Solution 2: WeakReference in custom pattern
public class WeakEvent<TArgs> where TArgs : EventArgs {
    private List<WeakReference<EventHandler<TArgs>>> _handlers = new();
    public void Subscribe(EventHandler<TArgs> h) =>
        _handlers.Add(new WeakReference<EventHandler<TArgs>>(h));
    public void Raise(object sender, TArgs args) {
        _handlers.RemoveAll(wr => !wr.TryGetTarget(out _));
        foreach (var wr in _handlers) {
            if (wr.TryGetTarget(out var h)) h(sender, args);
        }
    }
}

// Best practice: unsubscribe (-=) when done, or use
// IDisposable pattern with weak references for long-lived publishers
14

反射

类型信息

反射在运行时检查类型元数据。typeof(T) 在编译时获取 Type;obj.GetType() 在运行时获取。Type 提供 Name、IsClass、IsValueType 和枚举成员的方法(GetMethods、GetProperties、GetFields)。BindingFlags 控制返回什么(NonPublic、Instance、Static、Public)。反射强大但慢——尽可能缓存结果。

csharp
using System;

Type t = typeof(string);
// Or: Type t = "hello".GetType();

Console.WriteLine(t.Name);          // String
Console.WriteLine(t.FullName);      // System.String
Console.WriteLine(t.IsClass);       // true
Console.WriteLine(t.IsValueType);   // false
Console.WriteLine(t.IsAbstract);    // false

// Get methods
foreach (var m in t.GetMethods())
    Console.WriteLine(m.Name);

// Get properties
var props = t.GetProperties();

// Get fields
var fields = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

// Check if assignable
Type ienum = typeof(IEnumerable<int>);
Console.WriteLine(ienum.IsAssignableFrom(t));  // false

实例化与调用

Activator.CreateInstance 动态创建对象。MethodInfo.Invoke 通过反射调用方法(由于参数装箱和安全检查而慢)。对于重复调用,CreateDelegate 快得多——创建绕过反射开销的强类型委托。反射破坏编译时类型安全且比直接调用慢;用于框架(序列化、DI、ORM)和缓存场景。

csharp
using System.Reflection;

// Create instance
Type t = typeof(StringBuilder);
object obj = Activator.CreateInstance(t);
// Or with args: Activator.CreateInstance(t, "initial")

// Invoke method
MethodInfo method = t.GetMethod("Append", new[] { typeof(string) });
object result = method.Invoke(obj, new object[] { "hello" });

// Get/set property
PropertyInfo prop = t.GetProperty("Length");
int len = (int)prop.GetValue(obj);

// Get/set field
FieldInfo field = t.GetField("_field", BindingFlags.NonPublic | BindingFlags.Instance);
field.SetValue(obj, 42);

// Create delegate from MethodInfo (faster than Invoke)
var appendDel = (Func<StringBuilder, string, StringBuilder>)
    method.CreateDelegate(typeof(Func<StringBuilder, string, StringBuilder>));

特性

特性将元数据附加到代码元素(类、方法、属性)。用 [AttributeUsage(...)] 定义,指定有效目标。通过 GetCustomAttributes<T>()(泛型,现代)或 Attribute.GetCustomAttribute 检索。内置特性:[Obsolete]、[Serializable]、[DllImport]、[Conditional]。框架大量使用特性:[HttpGet](ASP.NET)、[JsonProperty](JSON)、[Table](EF Core)。

csharp
// Define an attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class AuthorAttribute : Attribute {
    public string Name { get; }
    public AuthorAttribute(string name) => Name = name;
}

// Apply
[Author("Alice")]
[Author("Bob")]
public class Widget {
    [Author("Charlie")]
    public void DoWork() { }
}

// Read via reflection
var attrs = typeof(Widget).GetCustomAttributes<AuthorAttribute>();
foreach (var a in attrs) Console.WriteLine(a.Name);  // Alice, Bob

// Check if attribute is applied
bool hasAuthor = Attribute.IsDefined(typeof(Widget), typeof(AuthorAttribute));

Emit 与动态代码生成

Reflection.Emit 在运行时生成 IL,用于动态场景(表达式编译、序列化器、模拟框架)的最大性能。你构建类型/方法并直接发出 IL 操作码。这是高级的——使用表达式树(Expression.Compile)进行更简单的动态代码。Emit 被 DLR(动态语言)、ORM 和序列化库用于在运行时生成快速类型化代码。

csharp
using System.Reflection;
using System.Reflection.Emit;

// Create a dynamic assembly/module/type
var asmName = new AssemblyName("Dynamic");
var asm = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
var mod = asm.DefineDynamicModule("Main");
var type = mod.DefineType("Calculator", TypeAttributes.Public);

// Add a method
var method = type.DefineMethod("Add",
    MethodAttributes.Public | MethodAttributes.Static,
    typeof(int), new[] { typeof(int), typeof(int) });

// Emit IL
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);  // load first arg
il.Emit(OpCodes.Ldarg_1);  // load second arg
il.Emit(OpCodes.Add);      // add
il.Emit(OpCodes.Ret);      // return

Type created = type.CreateType();
int result = (int)created.GetMethod("Add").Invoke(null, new object[] { 3, 4 });  // 7

表达式树

表达式树将代码表示为数据(Expression 节点的树)。它们是 IQueryable 的基础(LINQ 提供程序将它们转换为 SQL 等)。Compile() 将表达式转换为可运行的委托。可以手动构建表达式进行动态代码生成(比 Reflection.Emit 更可读)。表达式树启用 ORM、规则引擎和动态 LINQ。

csharp
using System.Linq.Expressions;

// Expression<Func<T>>: code as data (tree), not executable
Expression<Func<int, int, int>> expr = (a, b) => a + b + 1;

// Inspect the tree
Console.WriteLine(expr.Body.NodeType);  // Add
Console.WriteLine(expr.Body.Type);      // int

// Compile to a delegate (fast, runs in memory)
Func<int, int, int> compiled = expr.Compile();
int result = compiled(2, 3);  // 6

// EF Core translates expressions to SQL
Expression<Func<User, bool>> filter = u => u.Age > 18;
// → WHERE Age > 18

// Build expressions manually (advanced)
ParameterExpression x = Expression.Parameter(typeof(int), "x");
Expression body = Expression.Multiply(x, Expression.Constant(2));
var lambda = Expression.Lambda<Func<int, int>>(body, x);
Func<int, int> doubler = lambda.Compile();
15

扩展方法与模式

扩展方法

扩展方法让你在不修改现有类型的情况下“添加”方法。在静态类中定义,第一个参数带 'this'。它们是语法糖——编译器将 email.IsEmail() 重写为 StringExtensions.IsEmail(email)。LINQ 完全是 IEnumerable 上的扩展方法。使用扩展向密封类、接口或第三方类型添加实用方法。用 using 引入作用域。

csharp
public static class StringExtensions {
    // Must be static, in static class, first param with 'this'
    public static bool IsEmail(this string s) =>
        s.Contains('@') && s.Contains('.');

    public static string Repeat(this string s, int n) =>
        string.Concat(Enumerable.Repeat(s, n));

    // Chainable
    public static string Truncate(this string s, int max) =>
        s.Length <= max ? s : s[..max];
}

// Usage: appears as instance method
string email = "[email protected]";
bool valid = email.IsEmail();
string repeated = "ab".Repeat(3);  // "ababab"
string short_ = "hello world".Truncate(5);  // "hello"

// LINQ is built on extension methods (Enumerable class)
var evens = nums.Where(n => n % 2 == 0);  // extension

流式接口

流式接口返回 'this' 以启用方法链,产生读起来像句子的可读代码。常见于构建器(QueryBuilder)、配置(ASP.NET、EF)和 DSL。扩展方法可以向基本类型添加流式方法(5.Seconds())。不可变流式 API 保持方法纯净(返回新实例),或构建器修改并返回 this。

csharp
public class QueryBuilder {
    private string _table = "";
    private string _where = "";
    private int _limit;

    public QueryBuilder From(string table) { _table = table; return this; }
    public QueryBuilder Where(string clause) { _where = clause; return this; }
    public QueryBuilder Limit(int n) { _limit = n; return this; }

    public string Build() =>
        $"SELECT * FROM {_table} WHERE {_where} LIMIT {_limit}";
}

// Fluent chaining
var sql = new QueryBuilder()
    .From("users")
    .Where("age > 18")
    .Limit(10)
    .Build();

// Extension methods enable fluent APIs on existing types
public static class IntExtensions {
    public static TimeSpan Seconds(this int n) => TimeSpan.FromSeconds(n);
    public static TimeSpan Minutes(this int n) => TimeSpan.FromMinutes(n);
}
var delay = 5.Seconds();  // TimeSpan
var timeout = 2.Minutes();

迭代器方法(yield)

yield return 将方法转换为迭代器——编译器生成状态机。执行是惰性的:代码仅在拉取项时运行。这启用无限序列和高效流式传输(一次处理一项而无需缓冲)。yield break 结束迭代。方法必须返回 IEnumerable<T> 或 IEnumerator<T>。LINQ 运算符基于 yield 构建。重新枚举会重新运行方法。

csharp
// yield return: lazy iterator (state machine generated)
public static IEnumerable<int> Evens(int max) {
    for (int i = 0; i <= max; i += 2)
        yield return i;
}

// Lazy: nothing runs until enumerated
var evens = Evens(100);  // no execution yet
foreach (var e in evens) { /* runs here */ }

// yield break: end iteration
public static IEnumerable<T> TakeWhile<T>(IEnumerable<T> src, Func<T, bool> pred) {
    foreach (var item in src) {
        if (!pred(item)) yield break;
        yield return item;
    }
}

// Infinite sequence (lazy, safe)
public static IEnumerable<int> Naturals() {
    int n = 0;
    while (true) yield return n++;
}
var first10 = Naturals().Take(10);  // 0..9

依赖注入

依赖注入(DI)通过构造函数传递依赖项而不是内部创建。好处:可测试性(测试中替换实现)、松耦合、单一职责。.NET 内置 DI 容器(Microsoft.Extensions.DependencyInjection)处理注册和生命周期(AddTransient、AddScoped、AddSingleton)。始终依赖接口而非具体类以获得灵活性。

csharp
// Interface for the dependency
public interface IEmailSender {
    Task SendAsync(string to, string body);
}

// Implementation
public class SmtpEmailSender : IEmailSender {
    public async Task SendAsync(string to, string body) { /* SMTP */ }
}

// Consumer: depends on abstraction, not concretion
public class UserService {
    private readonly IEmailSender _email;
    public UserService(IEmailSender email) => _email = email;  // inject

    public async Task RegisterAsync(string email) {
        // ... save user ...
        await _email.SendAsync(email, "Welcome!");
    }
}

// Register in DI container (ASP.NET Core)
builder.Services.AddScoped<IEmailSender, SmtpEmailSender>();
builder.Services.AddScoped<UserService>();

// Resolve
var userService = app.Services.GetRequiredService<UserService>();

工厂与构建器模式

工厂模式集中对象创建,向调用者隐藏具体类(创建复杂或运行时选择类型时有用)。构建器模式用流式方法逐步构建复杂对象,避免“伸缩构造函数”(许多参数)。两者都提高可读性和可维护性。C# record 的 'with' 表达式通常为不可变数据取代构建器。

csharp
// Factory: create objects without specifying exact class
public interface IShape { void Draw(); }
public class Circle : IShape { public void Draw() => Console.WriteLine("○"); }
public class Square : IShape { public void Draw() => Console.WriteLine("□"); }

public static class ShapeFactory {
    public static IShape Create(string type) => type switch {
        "circle" => new Circle(),
        "square" => new Square(),
        _ => throw new ArgumentException()
    };
}

// Builder: step-by-step construction of complex objects
public class Pizza {
    public string Size { get; set; }
    public bool Cheese { get; set; }
    public bool Pepperoni { get; set; }
}
public class PizzaBuilder {
    private readonly Pizza _p = new();
    public PizzaBuilder WithSize(string s) { _p.Size = s; return this; }
    public PizzaBuilder AddCheese() { _p.Cheese = true; return this; }
    public PizzaBuilder AddPepperoni() { _p.Pepperoni = true; return this; }
    public Pizza Build() => _p;
}

var pizza = new PizzaBuilder()
    .WithSize("large").AddCheese().AddPepperoni().Build();
16

模式匹配(C# 7-10+)

类型模式与 is

模式匹配(C# 7+)一步结合类型检查和变量绑定:'o is string s' 检查并赋值。属性模式({ Length: > 3 })匹配对象属性。C# 9 添加 'not'、'and'、'or' 组合器。switch 表达式(C# 8)返回值并使用模式——比 if-else 链清晰得多。_ 是丢弃模式(默认情况)。

csharp
object o = "hello";

// Type pattern (C# 7)
if (o is string s) {
    Console.WriteLine(s.Length);  // s is string here
}

// Combined with null check
if (o is string { Length: > 3 } longStr) {
    Console.WriteLine(longStr);  // string with length > 3
}

// Negation and conjunction (C# 9)
if (o is not null) { /* ... */ }
if (o is string and not null) { /* ... */ }

// Switch on type
string Describe(object o) => o switch {
    int i => $"Integer: {i}",
    string s => $"String: {s}",
    double d => $"Double: {d}",
    null => "null",
    _ => "Unknown"
};

属性与位置模式

属性模式匹配对象属性:{ Prop: pattern }。位置模式通过 Deconstruct 方法解构(record 自动具有)。模式组合:可以嵌套并与关系运算符(<、> 等)组合。C# 10 允许嵌套属性简写(Name.Length 而不是 Name: { Length: })。模式匹配使复杂条件逻辑声明式且可读。

csharp
public record Point(int X, int Y);
public record Person(string Name, int Age);

// Property pattern
string Describe(Person p) => p switch {
    { Age: < 18 } => "Minor",
    { Age: >= 65 } => "Senior",
    { Name.Length: > 10 } => "Long name",
    { } => "Adult"  // non-null Person
};

// Positional pattern (deconstruction)
string Quadrant(Point p) => p switch {
    (0, 0) => "Origin",
    ( > 0, > 0) => "Q1",
    ( < 0, > 0) => "Q2",
    ( < 0, < 0) => "Q3",
    ( > 0, < 0) => "Q4",
    _ => "On axis"
};

// Nested patterns
bool IsValid(Person p) => p is { Name: { Length: > 0 }, Age: > 0 };
// C# 10: nested property shorthand
bool IsValid2(Person p) => p is { Name.Length: > 0, Age: > 0 };

关系与逻辑模式(C# 9)

C# 9 引入了关系模式(<、>、<=、>=)和逻辑组合器(and、or、not)。这些使 switch 表达式对范围检查和类别逻辑富有表现力。'or' 组合模式(任一匹配则匹配);'and' 要求两者;'not' 取反。括号分组优先级。这用声明式、穷举(编译器检查)的逻辑取代冗长的 if-else 链。

csharp
// Relational patterns: <, >, <=, >=
string Classify(int n) => n switch {
    < 0 => "Negative",
    0 => "Zero",
    > 0 and < 10 => "Small positive",
    >= 10 and < 100 => "Medium",
    >= 100 => "Large"
};

// Logical patterns: and, or, not
bool IsWeekend(DayOfWeek d) => d is DayOfWeek.Saturday or DayOfWeek.Sunday;

// Combined with type patterns
decimal GetDiscount(object customer) => customer switch {
    Student => 0.2m,
    Senior or Veteran => 0.15m,
    not null => 0.0m,
    null => throw new ArgumentNullException()
};

// Parenthesized patterns for grouping
bool IsValid(int n) => n is (> 0 and < 10) or (> 100 and < 200);

列表模式(C# 11)

列表模式(C# 11)匹配数组和列表的形状:空 []、单元素 [x]、精确 [a, b] 或带切片 [first, .., last]。切片模式(..)将零个或多个中间元素捕获到变量中。与 var 和守卫(when)组合,这启用对序列的表达式匹配。适用于解析、验证和算法实现。

csharp
// List patterns match array/list shapes
int[] nums = { 1, 2, 3, 4, 5 };

string Describe(int[] arr) => arr switch {
    [] => "Empty",
    [single] => $"Single: {single}",
    [first, second] => $"Pair: {first}, {second}",
    [first, .., last] => $"First: {first}, Last: {last}",  // slice
    [.., var last] => $"Last: {last}",
    _ => "Other"
};

// Slice pattern (..) captures middle elements
if (nums is [var first, .. var middle, var last]) {
    Console.WriteLine(`First {first}, middle count {middle.Length}, last {last}`);
}

// With guards
string Classify(int[] arr) => arr switch {
    [var a, var b] when a == b => "Equal pair",
    [var a, var b] => "Unequal pair",
    _ => "Other"
};

switch 表达式

switch 表达式(C# 8)是返回值的表达式,不同于 switch 语句。它们使用模式匹配(=>)且更简洁。元组切换((a, b) switch)处理多个值。编译器检查枚举的穷举性(缺少情况时警告)。除非想要运行时异常,否则始终包含默认(_)。返回值的逻辑优先使用 switch 表达式而非语句。

csharp
// Traditional switch statement (verbose)
string Old(int n) {
    switch (n) {
        case 1: return "one";
        case 2: return "two";
        default: return "many";
    }
}

// Switch expression (C# 8): expression, returns value
string New(int n) => n switch {
    1 => "one",
    2 => "two",
    _ => "many"
};

// With multiple values per arm
string Describe(int day, bool holiday) => (day, holiday) switch {
    (6 or 7, _) => "Weekend",
    (_, true) => "Holiday",
    (1, false) => "Monday",
    _ => "Weekday"
};

// Exhaustiveness: compiler warns if not all cases covered
// (especially with enums)
17

Record 与不可变性

Record 基础(C# 9)

Record(C# 9)是具有基于值相等性的引用类型——如果数据相等则两个 record 相等(不像使用引用相等性的类)。编译器自动生成 Equals、GetHashCode、ToString 和 Deconstruct。'with' 表达式创建修改属性的副本(非破坏性变更)。Record 非常适合 DTO、值对象和不可变数据模型。

csharp
// Record: value-equality, immutable, concise
public record Person(string Name, int Age);

// Create
var p1 = new Person("Alice", 30);
var p2 = new Person("Alice", 30);

// Value equality (auto-generated)
Console.WriteLine(p1 == p2);  // true (compares properties)
Console.WriteLine(p1.Equals(p2));  // true

// ToString (auto-generated, readable)
Console.WriteLine(p1);  // Person { Name = Alice, Age = 30 }

// Deconstruction
var (name, age) = p1;

// Non-destructive mutation (with-expression)
var older = p1 with { Age = 31 };
// p1 is unchanged, older is a copy with Age = 31

Record Struct(C# 10)

C# 10 添加了 record struct(具有 record 特性的值类型)和 readonly record struct(不可变值类型)。选择:record class(引用类型,用于较大/共享数据)、record struct(值类型,用于小数据,避免堆分配)、readonly record struct(不可变值类型,最安全)。record struct 具有值相等性和 'with' 支持像 record class。小型不可变值如 Point、Money 使用 readonly record struct。

csharp
// record class (default, reference type)
public record PersonRef(string Name, int Age);

// record struct (C# 10): value type with record features
public record struct Point(int X, int Y);

// readonly record struct: fully immutable value type
public readonly record struct Money(decimal Amount, string Currency);

// Mutable record struct (allowed but discouraged)
public record struct Counter(int Value) {
    public int Value { get; set; } = Value;
}

var p = new Point(1, 2);
var p2 = p with { X = 5 };  // copy with X=5
Console.WriteLine(p == p2);  // false

// Records can have additional members
public record Person(string Name) {
    public int Age { get; init; }  // extra init-only prop
    public string DisplayName => Name.ToUpper();
}

仅 init 设置器

仅 init 设置器(C# 9)允许在对象初始化期间设置但之后不能——构造后的不可变性而无需构造函数样板。'required'(C# 11)强制调用者在初始化器中设置属性(编译时检查)。record 默认使用 init 设置器。这启用具有对象初始化器语法的不可变对象模式,比构造函数参数更可读。

csharp
public class Config {
    // init: settable only during construction (object initializer)
    public string Name { get; init; } = "";
    public int Timeout { get; init; } = 30;
}

var c = new Config { Name = "MyApp", Timeout = 60 };
// c.Name = "Other";  // ERROR: init-only after construction

// Required properties (C# 11): must be set in initializer
public class User {
    public required string Email { get; init; }
    public string? DisplayName { get; init; }
}

// Must set Email, DisplayName optional
var u = new User { Email = "[email protected]" };

// Records use init by default (positional parameters)
public record Point(int X, int Y);
// Equivalent to: public int X { get; init; }
//                public int Y { get; init; }

Record 继承

record 支持继承:派生 record 在其构造函数中包含基 record 参数。'with' 表达式保留运行时类型(从 Dog 创建 Dog,而不是 Animal)。相等性检查运行时类型——两个 record 仅当它们是相同类型且数据相等时才相等。这使 record 在多态集合中正确工作,不像朴素的值相等性。

csharp
// Base record
public record Animal(string Name);

// Derived record: must include base parameters
public record Dog(string Name, string Breed) : Animal(Name);

// Derived can add to base
public record Teacher(string Name, string Subject) : Person(Name);

var d = new Dog("Rex", "Labrador");
Console.WriteLine(d);  // Dog { Name = Rex, Breed = Labrador }

// with preserves the runtime type
var d2 = d with { Name = "Buddy" };
Console.WriteLine(d2.GetType().Name);  // Dog (not Animal)

// Value equality across hierarchy
Animal a = new Dog("Rex", "Lab");
Animal b = new Dog("Rex", "Lab");
Console.WriteLine(a == b);  // true

// Different types are not equal even with same data
Animal c = new Animal("Rex");
Console.WriteLine(a == c);  // false (different runtime types)

Record 与类与结构体

类使用引用相等性(== 比较引用);record 使用值相等性(比较数据);结构体默认使用值相等性但是值类型(赋值时拷贝)。具有值语义的不可变数据(DTO、值对象、消息)使用 record。具有身份的可变实体(User、Order)和继承层次结构使用类。应被拷贝的小型(≤16 字节)值(Point、Color、Money)使用结构体。

csharp
// Class: reference type, reference equality
public class PersonClass {
    public string Name { get; set; }
    public PersonClass(string name) => Name = name;
}
var c1 = new PersonClass("Alice");
var c2 = new PersonClass("Alice");
Console.WriteLine(c1 == c2);  // false (different references)

// Record: reference type, value equality
public record PersonRec(string Name);
var r1 = new PersonRec("Alice");
var r2 = new PersonRec("Alice");
Console.WriteLine(r1 == r2);  // true (value equality)

// Struct: value type, value equality (manual)
public struct PointStruct {
    public int X, Y;
    public PointStruct(int x, int y) { X = x; Y = y; }
}
var s1 = new PointStruct(1, 2);
var s2 = new PointStruct(1, 2);
Console.WriteLine(s1.Equals(s2));  // true (default struct equality)

// Choose:
// - record: immutable data with value equality (DTOs, value objects)
// - class: mutable, reference semantics, inheritance (entities, services)
// - struct: small, value type, avoid heap allocation (Point, Color)
18

模式匹配深入

属性模式

属性模式内联匹配对象属性。switch 表达式直接返回值。组合模式(and/or)创建灵活条件。null 和 _(丢弃)处理边缘情况。这用声明式、可读的代码取代冗长的 if-else 链。

csharp
static decimal CalcDiscount(Customer c) => c switch
{
    { IsPremium: true, Orders.Count: > 100 } => 0.20m,
    { IsPremium: true } => 0.10m,
    { Orders.Count: > 50 } => 0.05m,
    { Country: "US" or "CA" } => 0.03m,
    null => 0m,
    _ => 0m
};

列表模式

列表模式(C# 11)匹配数组和可索引序列。[] 匹配空,[single] 匹配一个元素,[first, .. rest] 使用切片模式捕获剩余元素。适用于解析命令、验证序列和递归算法。

csharp
int Sum(int[] n) => n switch
{
    [] => 0,
    [int single] => single,
    [int first, int second] => first + second,
    [int first, .. int[] rest] => first + Sum(rest),
};

// Matching array shapes
var arr = new[] { 1, 2, 3, 4 };
// [1, ..] matches array starting with 1
// [.., 4] matches array ending with 4
// [1, 2, .., 4] matches 1,2,anything,4

元组模式

元组模式同时匹配多个值。每个情况测试值的元组。与关系模式(>、<、>=)和逻辑模式(and、or、not)组合,这创建强大的多维匹配而无需嵌套 if 语句。

csharp
static string Classify(int t, bool rain) => (t, rain) switch
{
    (< 0, _) => "Freezing",
    (>= 0 and < 15, true) => "Cold and wet",
    (>= 0 and < 15, false) => "Cold",
    (>= 15 and < 25, _) => "Mild",
    (>= 25, true) => "Hot and wet",
    (>= 25, false) => "Hot",
};

类型模式与守卫

类型模式匹配运行时类型。when 子句添加额外守卫。var 匹配任何内容(包括 null)。顺序很重要:更具体的模式必须在一般模式之前。这是现代 C# 中基于类型分派的惯用方式。

csharp
object Process(object input) => input switch
{
    int i when i > 0 => $"Positive: {i}",
    int i => $"Non-positive: {i}",
    string s when s.Length > 10 => s.Substring(0, 10),
    string s => s,
    IList list => $"List with {list.Count} items",
    var x => x?.ToString() ?? "null"
};

嵌套模式

模式任意深度嵌套。模式内的 var 捕获匹配值用于守卫或表达式体。此示例匹配 Line record 内的 Point 属性。嵌套模式对于简洁验证复杂对象图很强大。

csharp
record Point(int X, int Y);
record Line(Point Start, Point End);

string Describe(Line line) => line switch
{
    Line({ X: 0, Y: 0 }, { X: 0, Y: 0 }) => "Point at origin",
    Line({ X: var x1, Y: var y1 }, { X: var x2, Y: var y2 })
        when x1 == x2 && y1 == y2 => "Degenerate line",
    Line({ X: var x1 }, { X: var x2 }) when x1 == x2 => "Vertical",
    Line({ Y: var y1 }, { Y: var y2 }) when y1 == y2 => "Horizontal",
    _ => "Diagonal line"
};
19

Record 与 With 表达式

Record 值相等性

record 默认提供基于值的相等性:具有相同数据的两个实例相等。编译器生成 Equals、GetHashCode、ToString 和 == 运算符。record 是引用类型但为不可变性设计。用于 DTO、值对象和应按内容比较的数据。

csharp
public record Person(string Name, int Age);

var p1 = new Person("Alice", 30);
var p2 = new Person("Alice", 30);
Console.WriteLine(p1 == p2);  // True (value equality)
Console.WriteLine(p1.GetHashCode() == p2.GetHashCode()); // True
Console.WriteLine(p1);  // Person { Name = Alice, Age = 30 }

With 表达式

with 表达式创建修改属性的 record 副本。原始保持不变(非破坏性变更)。这是更新不可变数据的惯用方式。底层,编译器使用受保护的拷贝构造函数和仅 init 设置器。

csharp
var alice = new Person("Alice", 30);
var alice31 = alice with { Age = 31 };
var aliceMarried = alice with { Name = "Alice Smith" };

// Original is unchanged (immutable)
Console.WriteLine(alice.Age);      // 30
Console.WriteLine(alice31.Age);    // 31
Console.WriteLine(aliceMarried.Name); // Alice Smith

位置与仅 Init

位置 record 使用主构造函数语法并生成 deconstruct 方法。仅 init record 使用对象初始化器语法,允许默认值和 required 修饰符。简单值类型选择位置;具有可选或计算属性的复杂 record 选择仅 init。

csharp
// Positional record (primary constructor)
public record Point(int X, int Y);

// Init-only record (more flexible properties)
public record Employee
{
    public required string Name { get; init; }
    public decimal Salary { get; init; }
    public string Dept { get; init; } = "General";
}

var emp = new Employee { Name = "Bob", Salary = 50000 };
var promoted = emp with { Salary = 60000 };

Record Struct 与继承

record struct 是值类型(赋值时拷贝)且不能从其他 record struct 继承。引用类型 record 支持继承。readonly record struct 防止变更。小型不可变值使用 record struct;受益于引用语义和继承的较大对象使用 record class。

csharp
// Record struct (value type)
public readonly record struct Money(decimal Amount, string Currency);

// Record inheritance (reference type only)
public record Animal(string Name);
public record Dog(string Name, string Breed) : Animal(Name);

var dog = new Dog("Rex", "Labrador");
Console.WriteLine(dog is Animal);  // True
var copied = dog with { Breed = "Poodle" };

Deconstruct 与匹配

位置 record 自动生成 Deconstruct 方法,启用元组解构。这与模式匹配无缝集成:可以按位置或按名称匹配 record 属性。解构适用于 var 模式、元组模式和 switch 表达式。

csharp
var person = new Person("Alice", 30);
var (name, age) = person;  // Deconstruct
Console.WriteLine($"{name} is {age}");

// Works with pattern matching
string Greet(Person p) => p switch
{
    ("Alice", _) => "Hi Alice!",
    (_, < 18) => "Hello young one",
    var (n, a) => $"Hello {n}, age {a}"
};
20

源生成器

基本源生成器

源生成器在编译时运行并向编译添加 C# 源文件。IIncrementalGenerator 是现代 API(C# 9+)。RegisterPostInitializationOutput 添加静态代码而不分析用户代码。生成器是只读的:它们可以添加文件但不能修改现有代码。

csharp
[Generator]
public class HelloGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterPostInitializationOutput(ctx =>
        {
            ctx.AddSource("Hello.g.cs", """
                namespace Generated;
                public static class Hello
                {
                    public static string World => "Hello!";
                }
                """);
        });
    }
}

从特性生成

ForAttributeWithMetadataName 查找用特定特性标记的类型。管道:语法提供者过滤节点,转换提取数据,Collect 批处理结果,RegisterSourceOutput 发出代码。此增量管道缓存结果并仅在输入更改时重新生成,保持构建快速。

csharp
// Find types marked with an attribute
context.SyntaxProvider.ForAttributeWithMetadataName(
    "MyApp.AutoToStringAttribute",
    predicate: (node, _) => node is ClassDeclarationSyntax,
    transform: (ctx, _) => (ClassDeclarationSyntax)ctx.TargetNode)
    .Collect()
    .RegisterSourceOutput((spc, classes) =>
    {
        foreach (var cls in classes)
            GenerateToString(spc, cls);
    });

StringBuilder 输出

生成器以字符串形式产生源。StringBuilder 对多行输出高效。目标类必须是 partial 的以便生成器添加成员。生成的文件出现在 IDE 的 Dependencies > Analyzers 下。按约定使用 .g.cs 后缀区分生成文件。

csharp
void GenToString(SourceProductionContext ctx, ClassDeclarationSyntax cls)
{
    var sb = new StringBuilder();
    sb.AppendLine($"namespace {cls.ContainingNamespace};");
    sb.AppendLine($"public partial class {cls.Identifier}");
    sb.AppendLine("{");
    sb.AppendLine("  public override string ToString()");
    sb.AppendLine("  {");
    var props = cls.Members.OfType<PropertyDeclarationSyntax>();
    sb.AppendLine($"    return $"{cls.Identifier}: " +
      string.Join(", ", props.Select(p => $"{p.Identifier}={{{p.Identifier}}}")) + "";");
    sb.AppendLine("  }");
    sb.AppendLine("}");
    ctx.AddSource($"{cls.Identifier}_ToString.g.cs", sb.ToString());
}

JsonSerializerContext

System.Text.Json 包含内置源生成器,预编译序列化逻辑,消除运行时反射。这提高性能并支持 AOT 编译(Native AOT)。用 JsonSerializable 声明类型,然后使用生成的上下文进行序列化/反序列化调用。

csharp
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    WriteIndented = true)]
[JsonSerializable(typeof(List<Person>))]
[JsonSerializable(typeof(Dictionary<string, object>))]
public partial class AppJsonContext : JsonSerializerContext { }

// Usage (no runtime reflection):
var json = JsonSerializer.Serialize(people, AppJsonContext.Default.ListPerson);
var data = JsonSerializer.Deserialize(json, AppJsonContext.Default.ListPerson);

常见用例

流行的生成器用例:DTO 生成、DI 注册、强类型配置、AutoMapper 配置文件、日志模板和 JSON 序列化。MediatR、AutoMapper 和 Microsoft.Extensions.Logging 等库使用生成器减少样板并提高 AOT 兼容性。

csharp
// 1. DTO generation from interfaces
public interface IUserDto { string Name { get; } int Age { get; } }
// Generator creates: public class UserDto : IUserDto { ... }

// 2. Dependency injection registration
[RegisterService(ServiceLifetime.Scoped)]
public class MyService : IService { }
// Generator creates: services.AddScoped<MyService>();

// 3. Strongly-typed configuration
[ConfigurationSection("App:Db")]
public class DbSettings { public string Conn { get; set; } }
// Generator creates: services.Configure<DbSettings>(...)
21

Span 与 Memory

Span 基础

Span<T> 提供对连续内存的类型安全、内存安全视图而无需拷贝。它可以包装数组、stackalloc 内存、非托管内存或字符串。Span 是 ref struct(仅栈),因此不能被装箱、存储在字段中或被 lambda 捕获。用于高性能切片和解析。

csharp
// Span<T> is a stack-only view over contiguous memory
Span<int> numbers = stackalloc int[] { 1, 2, 3, 4, 5 };

// From array
int[] arr = { 10, 20, 30, 40, 50 };
Span<int> slice = arr.AsSpan(1, 3);  // [20, 30, 40]
slice[0] = 99;  // Modifies original array

// From string
ReadOnlySpan<char> text = "Hello, World".AsSpan();
ReadOnlySpan<char> hello = text.Slice(0, 5);  // "Hello"

用于异步的 Memory

Memory<T> 是 Span<T> 的堆安全对应物。它可以存储在字段中、被 lambda 捕获,并跨 await 边界使用。在实际工作时通过 .Span 转换为 Span<T>。异步 API 使用 Memory,同步热路径使用 Span。

csharp
// Memory<T> can live on the heap and cross async boundaries
async Task ProcessAsync(Memory<byte> buffer)
{
    await Task.Delay(100);
    Span<byte> span = buffer.Span;
    for (int i = 0; i < span.Length; i++)
        span[i] = (byte)(span[i] * 2);
}

byte[] data = new byte[1024];
await ProcessAsync(data);  // Implicit conversion to Memory<byte>

避免分配

Span 消除解析和切片期间的中间字符串分配。对于处理大输入的热路径(HTTP 解析、JSON、日志文件),这可以显著减少 GC 压力。int.TryParse 和其他基本类型有 Span 重载。仅在实际需要字符串时调用 ToString()。

csharp
// Traditional (allocates new string)
string Extract(string input, int start, int len)
    => input.Substring(start, len);

// Zero-allocation with Span
ReadOnlySpan<char> ExtractFast(ReadOnlySpan<char> input, int start, int len)
    => input.Slice(start, len);

// Parse without allocating substrings
bool TryParseInt(ReadOnlySpan<char> span, out int result)
    => int.TryParse(span, out result);

Stackalloc 与非托管

stackalloc 在调用栈上分配(自动释放,无 GC 压力)但限于小尺寸。对于大缓冲区,使用 NativeMemory.Alloc/Free(C# 7+)或 Marshal.AllocHGlobal。始终将非托管内存包装在 Span 中并在 finally 块中释放以防止泄漏。

csharp
// Stack allocation (small, fast, auto-freed)
Span<int> buffer = stackalloc int[128];
for (int i = 0; i < buffer.Length; i++)
    buffer[i] = i;

// Unmanaged memory (large buffers, manual lifetime)
unsafe
{
    int* ptr = (int*)NativeMemory.Alloc(1024, sizeof(int));
    try
    {
        Span<int> span = new Span<int>(ptr, 1024);
        span.Fill(42);
    }
    finally
    {
        NativeMemory.Free(ptr);
    }
}

Span 友好的 API

设计 API 接受 Span<T> 或 ReadOnlySpan<T> 用于缓冲区,避免数组分配。调用者决定使用 stackalloc、数组还是非托管内存。Span.TryWrite(C# 10+)直接格式化到 span 中而不分配字符串,适用于日志和序列化。

csharp
// Writing to a Span buffer
int WriteValues(Span<int> dest)
{
    int count = 0;
    foreach (var v in new[] { 1, 2, 3 })
    {
        if (count >= dest.Length) break;
        dest[count++] = v;
    }
    return count;
}

// Span-based string formatting (C# 10+)
Span<char> dest = stackalloc char[64];
bool ok = dest.TryWrite($"Value: {42:N2}", out int charsWritten);
Console.WriteLine(dest.Slice(0, charsWritten).ToString());
22

内插字符串深入

格式说明符

字符串插值($"...")在花括号中嵌入表达式。冒号后的格式说明符控制输出:C 表示货币,F 表示固定小数,X 表示十六进制,D 表示带填充的数字,P 表示百分比,yyyy-MM-dd 表示日期。编译器将其转换为 string.Format 或 FormattableString。

csharp
string name = "Alice";
int age = 30;
decimal price = 19.99m;
DateTime now = DateTime.Now;

Console.WriteLine($"Name: {name}, Age: {age}");
Console.WriteLine($"Price: {price:C}");  // $19.99
Console.WriteLine($"Date: {now:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Hex: {255:X}");  // FF
Console.WriteLine($"Padded: {age:D5}");  // 00030
Console.WriteLine($"Percent: {0.85:P0}");  // 85%

原始字符串插值

逐字内插字符串($@)按字面值保留反斜杠并允许多行内容。原始字符串插值(C# 11)使用三引号并消除所有转义:无需转义花括号或引号。$ 符号的数量控制插值需要多少花括号。

csharp
// Verbatim interpolated ($@)
string path = $@"C:\Users\{name}\Documents";
string json = $@"{{
  ""name"": ""{name}"",
  ""age"": {age}
}}";

// Raw string interpolation (C# 11)
string query = $$"""
    SELECT * FROM Users
    WHERE Name = '{{name}}'
    AND Age > {{age - 5}}
    """;
// $$ means {{ }} for interpolation, no escaping needed

FormattableString 与区域

FormattableString 保留插值结构,允许用特定区域延迟格式化。这对于不应依赖当前区域的库至关重要。FormattableString.Invariant 强制不变区域,防止序列化和日志中的区域特定问题。

csharp
// FormattableString captures the format for deferred execution
FormattableString msg = $"Hello {name}, you are {age} years old";

// Explicit culture (avoid CurrentCulture issues in libraries)
string german = msg.ToString(CultureInfo.GetCultureInfo("de-DE"));
string invariant = msg.ToString(CultureInfo.InvariantCulture);

// Invariant by default
string formatted = FormattableString.Invariant($"Total: {price}");

内插字符串处理程序

内插字符串处理程序(C# 10)让编译器将插值组件直接传递给方法,在不需要结果时避免字符串分配。这就是高性能日志的工作方式:如果日志级别被禁用,不构建字符串。处理程序分别接收字面量和格式化值。

csharp
// Custom handler (C# 10+) for conditional allocation
[InterpolatedStringHandler]
public ref struct LogStringHandler
{
    public LogStringHandler(int literal, int hole, Logger logger, out bool enabled)
    {
        enabled = logger.IsEnabled;
        // Skip allocation if logging is disabled
    }
    public void AppendLiteral(string s) { /* ... */ }
    public void AppendFormatted<T>(T value) { /* ... */ }
}

// Usage: no string allocation if log level is off
logger.Info($"Processing {item} with {count} items");

基于 Span 的格式化

Span.TryWrite 将内插字符串直接格式化到栈分配的缓冲区中,完全避免堆分配。string.Create 与内插字符串和区域一起使用,无需中间分配即可获得区域感知格式化。这些是高吞吐量应用热路径的基本技术。

csharp
// Format directly into a buffer (C# 10+)
Span<char> buffer = stackalloc char[256];

if (buffer.TryWrite($"User: {name}, Age: {age}", out int chars))
{
    var result = buffer[..chars];
    Console.WriteLine(result);
}

// Concatenation with DefaultInterpolatedStringHandler
string built = string.Create(CultureInfo.InvariantCulture, $"Total={price}");
23

文件范围命名空间

文件范围语法

文件范围命名空间(C# 10)用分号而不是花括号为整个文件声明命名空间。这移除一级缩进,使代码更扁平、更易读。每个文件只允许一个文件范围命名空间。文件中的所有类型都属于该命名空间。

csharp
// Traditional (adds a level of indentation)
namespace MyApp.Services
{
    public class UserService
    {
        public void Process() { }
    }
}

// File-scoped (C# 10+, no indentation)
namespace MyApp.Services;

public class UserService
{
    public void Process() { }
}

全局 using

全局 using(C# 10)在一个文件中声明,应用于项目中的所有文件。与文件范围命名空间组合实现干净、最小的样板。.NET SDK 项目为常见命名空间自动生成全局 using(ImplicitUsings)。顺序:全局 using,然后文件 using,然后命名空间。

csharp
// Global usings (apply to all files in the project)
global using System.Collections.Generic;
global using System.Linq;

// File-scoped namespace with usings
using System.Text.Json;
using MyApp.Models;

namespace MyApp.Api;

public class UserController
{
    // Can use List, LINQ, Json, Models without per-file usings
}

嵌套命名空间

文件范围命名空间使用点分名称表示层次结构:MyApp.Services.Authentication 等同于嵌套命名空间块。多个文件可以共享同一命名空间。不能在同一文件中在不同命名空间中嵌套类型;改用单独文件。

csharp
// File-scoped: flat name with dots
namespace MyApp.Services.Authentication;

public class TokenService { }

// Multiple files can use the same namespace
// File 1: namespace MyApp.Services;
// File 2: namespace MyApp.Services;  // Same namespace

项目配置

项目文件中的 ImplicitUsings 为常见 System 命名空间自动生成全局 using。与文件范围命名空间组合,这消除了大多数样板。Nullable 启用可空引用类型。这些是新的 .NET 6+ 项目的默认设置。

csharp
<!-- .csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

<!-- ImplicitUsings generates:
     global using System;
     global using System.Collections.Generic;
     global using System.IO;
     global using System.Linq;
     global using System.Net.Http;
     global using System.Threading;
     global using System.Threading.Tasks;
-->

迁移与最佳实践

使用 .editorconfig 强制文件范围命名空间(IDE0161)。IDE 提供从传统到文件范围的一键迁移。最佳实践:每个文件一个公共类型,文件名与类型名匹配。这保持文件小且导航方便。

csharp
// .editorconfig to enforce file-scoped namespaces
[*.{cs,vb}]
dotnet_diagnostic.IDE0161.severity = error  // Prefer file-scoped

// Migration: use the IDE fixer to convert
// Right-click > Quick Actions > Convert to file-scoped namespace

// Best practice: one type per file
// UserService.cs
namespace MyApp.Services;
public class UserService { }
24

必需成员

必需属性

required 修饰符(C# 11)强制调用者在对象创建期间初始化属性。如果未设置必需成员,编译器发出 CS9035。这取代了必需属性的构造函数验证。与仅 init 设置器和对象初始化器一起工作,提供编译时保证。

csharp
public class User
{
    public required Guid Id { get; init; }
    public required string Name { get; init; }
    public required string Email { get; init; }
    public string? Phone { get; init; }  // Optional
}

// Compiler enforces initialization
var user = new User
{
    Id = Guid.NewGuid(),
    Name = "Alice",
    Email = "[email protected]"
};

// Error CS9035: Required member 'Name' must be set
// var bad = new User { Id = Guid.NewGuid() };

SetsRequiredMembers

SetsRequiredMembers 特性告诉编译器构造函数初始化所有必需成员,允许调用者使用构造函数而无需对象初始化器。这桥接了必需属性与传统构造函数之间的差距。该特性在运行时不验证;它是编译时承诺。

csharp
public class Configuration
{
    public required string ConnectionString { get; init; }
    public required int Port { get; init; }

    [SetsRequiredMembers]
    public Configuration(string conn, int port)
    {
        ConnectionString = conn;
        Port = port;
    }
}

// Both work: constructor or initializer
var c1 = new Configuration("localhost", 5432);
var c2 = new Configuration
{
    ConnectionString = "localhost",
    Port = 5432
};

required 与 record

必需成员与 record 一起工作。对于位置 record,必需属性在体中声明,而不是主构造函数。with 表达式自动保留必需值。默认值(如 OpenedAt)使属性可选而无需 required 修饰符。

csharp
public record Account
{
    public required string AccountNumber { get; init; }
    public required decimal Balance { get; init; }
    public DateTime OpenedAt { get; init; } = DateTime.UtcNow;
}

// Positional record with required
public record Customer(string Name)
{
    public required string Email { get; init; }
}

var cust = new Customer("Alice") { Email = "[email protected]" };

init 与 required 与 set

init 使属性仅在构造期间可设置(之后不可变)。required 强制初始化。两者组合(required + init)创建必需的不可变属性。required + set 允许构造后修改。根据值是否应在创建后更改来选择。

csharp
// Init-only: can be set in initializer, but not required
public class Settings
{
    public string Name { get; init; } = "Default";
}

// Required + init: must be set, immutable after
public class SecureSettings
{
    public required string ApiKey { get; init; }
}

// Required + set: must be set, but can be modified later
public class MutableConfig
{
    public required string Environment { get; set; }
}

JSON 序列化

System.Text.Json 尊重 required 修饰符:如果 JSON 中缺少必需属性,反序列化抛出 JsonException。这提供与编译时契约匹配的运行时验证。与 init 组合用于保证填充所有必需字段的不可变 DTO。

csharp
using System.Text.Json;

public class ApiResponse
{
    public required string Status { get; init; }
    public required int Code { get; init; }
    public string? Message { get; init; }
}

// Deserialization enforces required at runtime
var json = """{"Status":"OK","Code":200}""";
var response = JsonSerializer.Deserialize<ApiResponse>(json);

// Throws JsonException if required member is missing
// var bad = JsonSerializer.Deserialize<ApiResponse>("""{"Code":200}""");
25

集合表达式

集合表达式语法

集合表达式(C# 12)为初始化任何集合类型提供统一语法 [..]。编译器推断类型并使用适当的构建器。这取代了冗长的 new List<int> { 1, 2, 3 } 语法。适用于数组、List<T>、Span<T>、IEnumerable<T> 和带 CollectionBuilder 特性的自定义集合。

csharp
// C# 12 collection expressions
int[] numbers = [1, 2, 3, 4, 5];
List<string> names = ["Alice", "Bob", "Charlie"];
Span<int> span = [10, 20, 30];
IEnumerable<int> seq = [1, 2, 3];

// Empty collections
int[] empty = [];
List<int> emptyList = [];

// Works with any collection type that has a builder
HashSet<string> set = ["a", "b", "c"];

展开运算符

展开运算符(..)将集合展平到封闭的集合表达式中。这取代了 Concat、AddRange 和手动循环。可以将展开与单个元素混合。展开适用于任何 IEnumerable<T>,使简洁地组合多个数据源变得容易。

csharp
int[] a = [1, 2, 3];
int[] b = [4, 5, 6];

// Spread elements with ..
int[] combined = [..a, ..b, 7, 8];  // [1,2,3,4,5,6,7,8]

// Mix spreads and literals
List<int> result = [0, ..a, 100, ..b];

// Spread any IEnumerable
List<int> evens = [.. Enumerable.Range(0, 10).Where(x => x % 2 == 0)];

返回与参数模式

集合表达式适用于 return 语句、参数和赋值。编译器自动转换为目标类型。条件展开(带 .. 的三元)让你可选地包含元素。这使从多个可选源构建集合干净且可读。

csharp
// Return collection expressions
List<int> GetNumbers() => [1, 2, 3];
int[] GetArray() => [10, 20];

// Pass as arguments
void Process(IEnumerable<int> data) { }
Process([1, 2, 3]);

// Conditional spread
bool includeExtra = true;
var items = [
    1, 2, 3,
    .. includeExtra ? [4, 5] : []
];

自定义集合构建器

CollectionBuilder 特性将集合类型连接到工厂方法,启用集合表达式支持。Create 方法接收元素的 ReadOnlySpan<T>。这允许第三方集合参与统一 [..] 语法。构建器模式保持初始化高效。

csharp
[CollectionBuilder(typeof(MyCollection), "Create")]
public class MyCollection<T> : IEnumerable<T>
{
    private readonly List<T> _items = new();
    public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();

    public static MyCollection<T> Create(ReadOnlySpan<T> items)
    {
        var col = new MyCollection<T>();
        foreach (var item in items) col._items.Add(item);
        return col;
    }
}

// Now usable with collection expressions
MyCollection<int> custom = [1, 2, 3, 4];

params 集合

C# 12 允许 params 与任何集合类型一起使用,不仅是数组。集合表达式可以传递给 params 方法。当目标是 Span<T> 时,编译器可能对小集合使用栈分配,完全避免堆分配。这使可变参数方法既符合人体工程学又高效。

csharp
// C# 12: params with collection expressions
void Log(params string[] messages)
{
    foreach (var m in messages) Console.WriteLine(m);
}

// Traditional params call
Log("Error", "Warning", "Info");

// Collection expression call (C# 12)
Log(["Error", "Warning", "Info"]);

// params with Span for zero-allocation
void ProcessFast(params Span<int> values) { }
ProcessFast([1, 2, 3]);  // May use stackalloc internally

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。