C#(基础)

3 阅读34分钟

一、类型

完整总结:float / double / int / long / decimal

1. 核心特性速查表

类型存储大小取值范围精确性能否存小数典型场景
float32位 (4字节)±1.4e-45 ~ ±3.4e38❌ 不精确 (约7位有效数字)✅ 能图形学、GPU、嵌入式
double64位 (8字节)±4.9e-324 ~ ±1.8e308❌ 不精确 (约15-16位有效数字)✅ 能普通小数默认选择、科学计算
int32位 (4字节)-2,147,483,648 ~ 2,147,483,647 (约±21亿)绝对精确 (整数范围内)❌ 不能普通整数默认选择、ID、数量、索引
long64位 (8字节)-2⁶³ ~ 2⁶³-1 (约±9.22e18)绝对精确 (整数范围内)❌ 不能超大整数、时间戳、文件大小
decimal变长 (通常12-16字节)可自定义 (如 DECIMAL(20,10))绝对精确 (十进制)✅ 能金钱、财务、任何不能有误差的计算

2. 选型决策树 (从最常用到最专用)

你需要存什么?

├─ 整数?
│   ├─ 普通范围 (绝对值 ≤ 21亿) → ✅ int (最常用,90%的整数场景)
│   └─ 超大范围 (可能超过21亿) → ✅ long (ID、时间戳、文件大小)
│
├─ 小数?
│   ├─ 这是“钱”吗?(金额、汇率、税率、余额)
│   │   └─ 是 → ✅ decimal (必须精确,性能换精度)
│   │
│   └─ 不是钱 (坐标、温度、物理计算、百分比)
│       ├─ 普通业务代码、科学计算 → ✅ double (默认选择,精度足够)
│       └─ 特殊场景:内存/带宽极度紧张、GPU大批量计算 → ✅ float

3. 各类型一句话总结

类型一句话总结
int整数的默认选择。覆盖21亿范围,90%的整数场景用它就够了。
long超大整数的选择。当 int 不够用时(时间戳、文件大小、数据库ID)上它。
double小数的默认选择。不存钱的时候无脑用它,精度足够且性能好。
float省内存专用的double。除非你要存几百万个小数且内存吃紧,否则用 double
decimal钱的专用类型。用性能换绝对精确,财务系统必选,其他场景不推荐。

4. 实战代码示例 (Java)

using System;

namespace NumberTypesDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("=== C# 数值类型对比 ===\n");
            
            // ========================================
            // 1. int (32位整数)
            // ========================================
            int itemCount = 100;
            int userId = 1234567;
            int maxInt = int.MaxValue;  // 2,147,483,647
            
            Console.WriteLine("【int】32位整数");
            Console.WriteLine($"  商品数量: {itemCount}");
            Console.WriteLine($"  用户ID: {userId}");
            Console.WriteLine($"  int最大值: {maxInt:N0}");
            Console.WriteLine($"  超过最大值会溢出: {(int)(maxInt + 1)}");  // 溢出变成负数
            Console.WriteLine();
            
            // ========================================
            // 2. long (64位整数)
            // ========================================
            long orderId = 9876543210123456L;     // 注意 L 后缀
            long fileSize = 5_000_000_000L;        // 5GB,下划线可提高可读性
            long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            long maxLong = long.MaxValue;           // 9,223,372,036,854,775,807
            
            Console.WriteLine("【long】64位整数");
            Console.WriteLine($"  订单号: {orderId}");
            Console.WriteLine($"  文件大小: {fileSize:N0} 字节");
            Console.WriteLine($"  当前时间戳: {timestamp}");
            Console.WriteLine($"  long最大值: {maxLong:N0}");
            Console.WriteLine();
            
            // ========================================
            // 3. float (32位浮点数,约7位有效数字)
            // ========================================
            float temperature = 36.5f;              // 注意 f 后缀
            float gpuWeight = 0.1234567f;
            float floatPi = 3.14159265358979f;
            
            Console.WriteLine("【float】32位浮点数(单精度)");
            Console.WriteLine($"  体温: {temperature}");
            Console.WriteLine($"  GPU权重: {gpuWeight}");
            Console.WriteLine($"  圆周率(单精度): {floatPi}");  // 只显示约7位有效数字
            Console.WriteLine($"  精度损失: 0.1f + 0.2f = {0.1f + 0.2f}");  // 不精确
            Console.WriteLine($"  与0.3比较: {(0.1f + 0.2f == 0.3f)}");  // False!
            Console.WriteLine();
            
            // ========================================
            // 4. double (64位浮点数,约15-16位有效数字)
            // ========================================
            double price = 19.99;                   // 默认就是 double,不需要后缀
            double science = 1.2345678901234567;
            double doublePi = 3.14159265358979323846;
            
            Console.WriteLine("【double】64位浮点数(双精度)");
            Console.WriteLine($"  价格: {price:C}");               // C 格式化为货币
            Console.WriteLine($"  科学计算: {science}");
            Console.WriteLine($"  圆周率(双精度): {doublePi}");
            Console.WriteLine($"  精度损失更小: 0.1 + 0.2 = {0.1 + 0.2}");
            Console.WriteLine($"  但与0.3比较: {(0.1 + 0.2 == 0.3)}");  // 仍然是 False!
            Console.WriteLine();
            
            // ========================================
            // 5. decimal (128位定点数,绝对精确)
            // ========================================
            decimal amount = 9.99m;                 // 注意 m 后缀
            decimal tax = 0.05m;
            decimal interest = 0.0012345m;          // 支持任意小数位
            
            decimal result = amount + (amount * tax);
            
            Console.WriteLine("【decimal】128位定点数(高精度)");
            Console.WriteLine($"  金额: {amount:C}");
            Console.WriteLine($"  税率: {tax:P}");                  // P 格式化为百分比
            Console.WriteLine($"  计算结果: {result:C}");
            Console.WriteLine($"  精确运算: 0.1m + 0.2m = {0.1m + 0.2m}");
            Console.WriteLine($"  与0.3比较: {(0.1m + 0.2m == 0.3m)}");  // True!
            Console.WriteLine();
            
            // ========================================
            // 6. 实战:存钱到底该用谁?
            // ========================================
            Console.WriteLine("=== 实战:金额计算(千万不要用double/float)===");
            
            // ❌ 错误示范:用 double 存钱
            double priceDouble = 9.99;
            double quantityDouble = 3;
            double totalDouble = priceDouble * quantityDouble;
            Console.WriteLine($"  double计算: {priceDouble} x {quantityDouble} = {totalDouble}");  // 可能 29.969999999999999
            
            // ❌ 更可怕的问题:累加
            double sumDouble = 0;
            for (int i = 0; i < 100; i++) sumDouble += 0.01;
            Console.WriteLine($"  double累加100次0.01: {sumDouble}");  // 0.9999999999999989,不是1.00!
            
            // ✅ 正确示范:用 decimal 存钱
            decimal priceDecimal = 9.99m;
            decimal quantityDecimal = 3;
            decimal totalDecimal = priceDecimal * quantityDecimal;
            Console.WriteLine($"  decimal计算: {priceDecimal} x {quantityDecimal} = {totalDecimal}");  // 准确 29.97
            
            decimal sumDecimal = 0;
            for (int i = 0; i < 100; i++) sumDecimal += 0.01m;
            Console.WriteLine($"  decimal累加100次0.01: {sumDecimal}");  // 准确 1.00
            
            Console.WriteLine("\n按任意键退出...");
            Console.ReadKey();
        }
    }
}

5. 常见错误提醒

错误写法问题正确写法
float price = 9.99;小数默认是 double,直接赋值 float 会报精度损失float price = 9.99f; 或直接用 double
long id = 9999999999;数字超过 int 范围,编译报错long id = 9999999999L; (加 L 后缀)
double money = 0.1 + 0.2;结果 ≈ 0.30000000000000004,不精确BigDecimal 或整数存“分”
int count = 3000000000;超过21亿,溢出变成负数改用 long

6、强制转换

  • 如果用var定义数据,会自动推断类型,小数默认是double类型,整数默认是int类型

只有小范围的转换成大范围的不需要强制转换,反之就得要。 强制转换:eg:weight = (float)height;

二、类

  • 如果类里定义的数据有required,在使用的时候就必须传入该字段的值,例如 Student s= new Student{ Name="yyy"};
  • 如果没有required,在使用的时候就可以不用传入值,例如Person p = new Person();
  • 如果有static 的数据,就得直接Person._name获取,因为这是静态数据,这是不会变的,就算实例化了多个实例,这个值也不会变。
  • 类里定义的数据
namespace ConsoleApp1
{
    class Person
    {
        public int Id;
        public string? Name;
        public static string? _name;
    }
    class Student
    {
        public int Id;
        public required string Name;
    }
    class Program
    {
        static void Main()
        {
            
            Person p = new Person();  
            p.Id = 10;
            Console.WriteLine($"{p.Id}+{p.Name}");
            Person._name = "88";
            Console.WriteLine($"{p.Id}+{p.Name}+{Person._name}");

            Student s= new Student{ Name="yyy"};
            s.Id=90;
            Console.WriteLine(s.Name);
        }
    }
}

不同类型的默认值

class Person
{
    public int Id;           // 默认值 = 0
    public string? Name;     // 默认值 = null
    public bool IsActive;    // 默认值 = false
    public double Score;     // 默认值 = 0.0
    public DateTime Birth;   // 默认值 = DateTime.MinValue
}

当你 new Person() 时,所有字段都会被自动赋值为该类型的默认值

required 的作用

required 是 C# 11 引入的特性,它的目的是强制调用方在对象初始化时显式赋值,而不是依赖默认值。

class Student
{
    public int Id;                    // 可以不赋值,默认为 0
    public required string Name;      // 必须赋值,不能是 null
}

// ✅ 正确:Name 被显式赋值了
Student s1 = new Student { Name = "张三", Id = 10 };

// ❌ 编译错误:未给 required 成员 Name 赋值
Student s2 = new Student { Id = 10 };

// ❌ 编译错误:required 成员不能赋值为 null
Student s3 = new Student { Name = null, Id = 10 };

1. public vs private 示例

class Person
{
    public int Id;      // 任何人都能访问和修改
    private int age;    // 只有 Person 类内部能访问
    
    public void SetAge(int value)
    {
        if (value > 0 && value < 150)  // 可以在内部做验证
            age = value;
    }
    
    public int GetAge()
    {
        return age;
    }
}

class Program
{
    static void Main()
    {
        Person p = new Person();
        
        p.Id = 10;        // ✅ 可以,Id 是 public
        // p.age = 20;    // ❌ 编译错误,age 是 private,外部不能访问
        
        p.SetAge(20);     // ✅ 通过 public 方法间接访问
        Console.WriteLine(p.GetAge());  // 20
    }
}

2. 访问修饰符完整对比

class Person
{
    public string Name;      // 任何地方(类内部、子类、其他类)都能访问
    private int age;         // 只有 Person 类内部能访问
    protected string IdCard; // Person 类 + 子类能访问
    internal string Dept;    // 同一个程序集内能访问
    private protected int Salary;   // 同一个程序集内的子类能访问
    protected internal string Email; // 子类 或 同一个程序集 能访问
}

private 保护数据

// ❌ 不好:直接暴露字段
class BankAccount_bad
{
    public decimal Balance;  // 任何人都能直接修改,无法控制
}

// ✅ 好:用 private 字段 + public 方法/属性
class BankAccount_good
{
    private decimal balance;  // 外部不能直接访问
    
    public decimal Balance    // 通过只读属性暴露
    {
        get { return balance; }
    }
    
    public void Deposit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("存款金额必须大于0");
        balance += amount;
    }
    
    public void Withdraw(decimal amount)
    {
        if (amount <= 0 || amount > balance)
            throw new ArgumentException("金额无效或余额不足");
        balance -= amount;
    }
}

required 的结合使用

class Student
{
    private int id;                      // private 字段
    private string name = null!;         // private 字段
    
    public int Id                        // public 属性
    {
        get { return id; }
        set { id = value; }
    }
    
    public required string Name          // required + public 属性
    { 
        get { return name; }
        set { name = value; }
    }
}

// 使用
Student s = new Student { Name = "张三" };  // required 强制要求
s.Id = 90;                                   // public 允许访问
// s.name = "李四";  // ❌ private,外部不能直接访问

之前的代码优化

// 原始代码(不够好)
class Person
{
    public int Id;           // 公开字段,任何人都能随意修改
    public string? Name;     // 同上
}

// 优化后(推荐写法)
class Person
{
    private int id;
    private string? name;
    
    public int Id
    {
        get { return id; }
        set 
        { 
            if (value <= 0)
                throw new ArgumentException("ID 必须大于0");
            id = value;
        }
    }
    
    public string? Name
    {
        get { return name; }
        set { name = value; }
    }
}

// 或者用自动属性(C# 简洁写法)
class Person_V2
{
    public int Id { get; set; }           // 编译器自动生成 private 字段
    public string? Name { get; set; }
}

// 只读属性(只能在构造函数中赋值)
class Person_V3
{
    public int Id { get; }                // 只有 get,外部不能修改
    public string Name { get; }
    
    public Person_V3(int id, string name)
    {
        Id = id;
        Name = name;
    }
}

一句话总结

关键字一句话
public"谁都能用" — 对外公开的接口
private"只有我自己能用" — 隐藏内部实现细节
required"必须给我赋值" — 防止忘记初始化

它们解决不同的问题public/private"谁能访问"required"有没有赋值" 。两者可以组合使用,比如 public required string Name { get; set; }

为什么需要 get/set

场景1:赋值时要验证(最常用)

// ❌ 用 public 字段 - 无法阻止
class Student_bad
{
    public int Age;  // 任何人都能赋任何值
}

var s = new Student_bad();
s.Age = -999;  // 编译通过!你的系统里出现了一个-999岁的学生


// ✅ 用 get/set - 可以在赋值时拦截
class Student_good
{
    private int _age;
    
    public int Age
    {
        get { return _age; }
        set 
        { 
            if (value < 0 || value > 150)
                throw new ArgumentException("年龄必须在0-150之间");
            _age = value;
        }
    }
}

var s2 = new Student_good();
s2.Age = -999;  // 异常!程序崩溃,而不是存错误数据

场景2:读取时进行计算

class Rectangle
{
    private double _width;
    private double _height;
    
    public double Width
    {
        get { return _width; }
        set { _width = value; }
    }
    
    public double Height
    {
        get { return _height; }
        set { _height = value; }
    }
    
    // 这个属性是"计算出来的",没有对应的字段
    public double Area
    {
        get { return _width * _height; }  // 只有 get,没有 set
    }
}

var rect = new Rectangle();
rect.Width = 10;
rect.Height = 20;
Console.WriteLine(rect.Area);  // 200,自动计算

场景3:只读或只写

class BankAccount
{
    private decimal _balance;
    
    // 只读属性:外界只能看,不能改
    public decimal Balance
    {
        get { return _balance; }
        // 没有 set!
    }
    
    // 只能通过方法修改
    public void Deposit(decimal amount)
    {
        if (amount > 0)
            _balance += amount;
    }
    
    public void Withdraw(decimal amount)
    {
        if (amount > 0 && amount <= _balance)
            _balance -= amount;
    }
}

var account = new BankAccount();
account.Deposit(100);
Console.WriteLine(account.Balance);  // 100
// account.Balance = 500;  // ❌ 编译错误!不能直接赋值

场景4:触发其他逻辑

{
    private string _name;
    
    public string Name
    {
        get { return _name; }
        set 
        { 
            _name = value;
            // 名字改了,顺便更新时间戳
            LastModified = DateTime.Now;
        }
    }
    
    public DateTime LastModified { get; private set; }
}

var p = new Person();
p.Name = "张三";
Console.WriteLine(p.LastModified);  // 刚刚的时间

场景5:延迟加载或缓存

class DataService
{
    private List<string> _data;
    
    public List<string> Data
    {
        get
        {
            // 第一次访问时才加载(不是创建对象时)
            if (_data == null)
            {
                _data = LoadFromDatabase();  // 耗时操作只做一次
            }
            return _data;
        }
    }
    
    private List<string> LoadFromDatabase()
    {
        // 假装从数据库加载
        return new List<string> { "a", "b", "c" };
    }
}

对比表格:字段 vs 属性

特性public 字段get/set 属性
代码简洁度✅ 一行搞定❌ 需要更多代码
验证数据❌ 无法验证✅ 可以在 set 里验证
计算属性❌ 只能存数据✅ 可以动态计算
只读控制❌ 无法控制✅ 可以只有 get
断点调试❌ 无法中断✅ 可以在 get/set 打断点
触发事件❌ 不行✅ 可以在 set 里触发
改变取值逻辑❌ 不行✅ 可以在 get 里变换
二进制序列化⚠️ 某些框架不支持✅ 广泛支持

自动属性:简洁又灵活

C# 提供了自动属性,让你既有 get/set 的灵活性,又不用写那么多代码:

// 自动属性 - 编译器自动生成隐藏的字段
class Person
{
    public int Id { get; set; }           // 可读可写
    public string Name { get; set; } = ""; // 带默认值
    public DateTime CreatedAt { get; }     // 只读属性(只能在构造函数赋值)
    
    public Person()
    {
        CreatedAt = DateTime.Now;  // 构造函数中赋值
    }
}

// 这10个字的代码,效果等同于:
// private int _id;
// public int get_Id() { return _id; }
// public void set_Id(int value) { _id = value; }

当你暂时不需要验证逻辑时,用自动属性就行。以后需要加验证时,再改成完整属性:

// 步骤1:先这样写
public int Age { get; set; }

// 步骤2:以后需要验证了,改成这样
private int _age;
public int Age
{
    get { return _age; }
    set 
    { 
        if (value < 0) throw new ArgumentException("年龄不能为负");
        _age = value;
    }
}

关键点:改成完整属性后,所有使用 person.Age 的代码完全不需要修改。如果用 public 字段,将来想加验证时,所有调用方代码都要改。

三、语法糖

1. 自动属性(最常用)

// 语法糖 ✨
public int Id { get; set; }

// 编译器实际生成的代码
private int _id;
public int get_Id() { return _id; }
public void set_Id(int value) { _id = value; }

2. var 隐式类型

// 语法糖 ✨
var name = "张三";      // 编译器自动推断是 string
var count = 10;         // int
var list = new List<int>();  // List<int>

// 实际没区别,编译后类型是确定的
string name = "张三";
int count = 10;
List<int> list = new List<int>();

3. 字符串插值

string name = "张三";
int age = 18;

// 语法糖 ✨
Console.WriteLine($"我叫{name},今年{age}岁");

// 原来的写法
Console.WriteLine(string.Format("我叫{0},今年{1}岁", name, age));
Console.WriteLine("我叫" + name + ",今年" + age + "岁");

4. => 表达式体(Lambda 箭头)

class Person
{
    // 语法糖 ✨ 方法
    public int Add(int a, int b) => a + b;
    
    // 语法糖 ✨ 只读属性
    public string FullName => $"{FirstName} {LastName}";
    
    // 原来的写法
    public int Add(int a, int b) 
    { 
        return a + b; 
    }
}

5. 空条件运算符 ?.

Person p = null;

// 语法糖 ✨
string name = p?.Name;  // p 是 null 就不访问 Name,直接返回 null

// 原来的写法
string name = null;
if (p != null)
{
    name = p.Name;
}

// 配合 null 合并运算符 ?? 
string displayName = p?.Name ?? "匿名用户";

6. 对象初始化器

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// 语法糖 ✨
var p = new Person { Name = "张三", Age = 18 };

// 原来的写法
var p = new Person();
p.Name = "张三";
p.Age = 18;

7. 集合初始化器

// 语法糖 ✨
var list = new List<int> { 1, 2, 3, 4 };
var dict = new Dictionary<string, int> 
{
    { "a", 1 },
    { "b", 2 }
};

// 原来的写法
var list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
list.Add(4);

8. using 简化(资源管理)

// 语法糖 ✨(C# 8+)
using var file = new StreamReader("test.txt");
string content = file.ReadToEnd();
// 离开作用域时自动调用 Dispose

// 原来的写法
StreamReader file = new StreamReader("test.txt");
try
{
    string content = file.ReadToEnd();
}
finally
{
    if (file != null) file.Dispose();
}

9. nameof 表达式

// 语法糖 ✨
Console.WriteLine(nameof(Person.Name));  // 输出 "Name"

// 原来的写法(硬编码)
Console.WriteLine("Name");  // 但重构时不会自动改

// 好处:重命名变量时,nameof 会自动更新

10. 索引器

class MyCollection
{
    private string[] _items = new string[10];
    
    // 语法糖 ✨
    public string this[int index]
    {
        get => _items[index];
        set => _items[index] = value;
    }
    
    // 使用
    var c = new MyCollection();
    c[0] = "hello";  // 像数组一样访问
}

// 这实际上是 get_Item 和 set_Item 方法的语法糖

11. 扩展方法

// 语法糖 ✨
public static class StringExtensions
{
    public static bool IsNullOrEmpty(this string str)
    {
        return string.IsNullOrEmpty(str);
    }
}

string s = "hello";
bool result = s.IsNullOrEmpty();  // 像实例方法一样调用

// 实际编译成
bool result = StringExtensions.IsNullOrEmpty(s);

12. 元组解构

// 语法糖 ✨
var (name, age) = GetPerson();  // 一次性声明多个变量
Console.WriteLine($"{name}, {age}");

(int id, string name) = GetUser();

// 原来的写法
var person = GetPerson();
string name = person.Item1;
int age = person.Item2;

13. switch 表达式

// 传统 switch(C# 7 之前)
string GetGrade(int score)
{
    switch (score)
    {
        case >= 90: return "A";
        case >= 80: return "B";
        case >= 70: return "C";
        default: return "F";
    }
}

// 语法糖 ✨(C# 8+ 表达式)
string GetGrade(int score) => score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    _ => "F"
};

14. using static 静态导入

// 语法糖 ✨
using static System.Math;
using static System.Console;

WriteLine(Sqrt(100));  // 不需要写 Math.Sqrt 和 Console.WriteLine

// 原来是
Console.WriteLine(Math.Sqrt(100));

15. => 定义 Lambda(最原始的形式)

// 语法糖 ✨
Func<int, int, int> add = (a, b) => a + b;

// 原来的写法(匿名委托)
Func<int, int, int> add = delegate(int a, int b) 
{ 
    return a + b; 
};

四、值引用和引用类型

1. 内存图理解

值类型:
int a = 10;
int b = a;    // 复制数据

b = 20;

a → [10]      a 还是 10,不开门
b → [20]      b 变成 20


引用类型:
Person p1 = new Person { Name = "张三" };
Person p2 = p1;    // 复制地址,p1 和 p2 指向同一个人

p2.Name = "李四";

p1 → [地址 0x1234] ──┐
                     │
p2 → [地址 0x1234] ──┘
                    ↓
              ┌─────────────┐
              │ Name = "李四" │
              └─────────────┘

p1.Name 也变成 "李四"!因为改的是同一块数据

2. 完整对比表格

对比项值类型引用类型
C# 关键字int, long, float, double, bool, char, decimal, struct, enumclass, interface, delegate, record, string, array, object
默认值0, false, 0.0等(不是null)null
可以 null?❌ 默认不能(除非用 int?✅ 可以
内存位置栈(局部变量)
赋值操作复制整个数据复制引用地址
== 含义比较值比较引用地址(是否同一对象)
性能栈上分配快,复制大数据慢堆上分配慢,GC 有开销

3. 核心代码示例

值类型:赋值后独立

int a = 10;
int b = a;    // b 复制了 a 的值 10

b = 20;       // 修改 b

Console.WriteLine(a);  // 10(没变)
Console.WriteLine(b);  // 20

// 结构体也是值类型
Point p1 = new Point { X = 1, Y = 2 };
Point p2 = p1;         // 复制整个结构体

p2.X = 100;

Console.WriteLine(p1.X);  // 1(没变)
Console.WriteLine(p2.X);  // 100

引用类型:赋值后共享

class Person
{
    public string Name { get; set; }
}

Person p1 = new Person { Name = "张三" };
Person p2 = p1;    // p2 复制的是 p1 的地址,指向同一个对象

p2.Name = "李四";

Console.WriteLine(p1.Name);  // "李四"(改了!)
Console.WriteLine(p2.Name);  // "李四"

// 判断是否同一对象
Console.WriteLine(p1 == p2);  // True(同一个地址)

4. 特殊情况:string

string 是引用类型,但行为像值类型(不可变):

string s1 = "hello";
string s2 = s1;    // s2 指向同一个 "hello"

s2 = "world";      // 不是修改原对象,而是新建一个 "world"

Console.WriteLine(s1);  // "hello"(没变!)
Console.WriteLine(s2);  // "world"

// 因为 string 是不可变的,每次修改都会创建新对象

为什么这样做?为了安全性和字符串池优化。

5. 方法参数传递

// 值类型参数:传入的是副本
void ChangeInt(int x)
{
    x = 100;
}

int a = 10;
ChangeInt(a);
Console.WriteLine(a);  // 10(没变)


// 引用类型参数:传入的是地址副本,但指向同一对象
void ChangeName(Person p)
{
    p.Name = "李四";
}

Person p1 = new Person { Name = "张三" };
ChangeName(p1);
Console.WriteLine(p1.Name);  // "李四"(变了)


// 想改值类型本身?用 ref
void ChangeIntRef(ref int x)
{
    x = 100;
}

int b = 10;
ChangeIntRef(ref b);
Console.WriteLine(b);  // 100(变了)

6. 常见陷阱和解决方案

陷阱1:把 struct 当 class 用

// 定义结构体(值类型)
struct Point
{
    public int X { get; set; }
}

// 定义类(引用类型)
class PointClass
{
    public int X { get; set; }
}

// 用法
Point p1 = new Point { X = 1 };
Point p2 = p1;
p2.X = 100;
Console.WriteLine(p1.X);  // 1(注意:要的就是这个效果?)

PointClass pc1 = new PointClass { X = 1 };
PointClass pc2 = pc1;
pc2.X = 100;
Console.WriteLine(pc1.X);  // 100(不同的效果)

陷阱2:集合中的值类型

List<Point> points = new List<Point>();
points.Add(new Point { X = 1, Y = 2 });

// ❌ 错误:无法修改集合中的值类型
// points[0].X = 100;  // 编译错误

// ✅ 正确:取出修改再放回
Point temp = points[0];
temp.X = 100;
points[0] = temp;

陷阱3:default 值不同

int i = default;        // 0
Person p = default;     // null(不是默认对象!)

// 要创建默认对象需要
Person p2 = new Person();  // 有参构造才创建

7. 如何选择:struct vs class

场景用 struct(值类型)用 class(引用类型)
数据大小(≤16字节推荐)大或小都可以
语义代表一个(如点、颜色、复数)代表一个对象(如人、订单、数据库连接)
修改频率创建后少修改经常修改
装箱风险经常装箱就不好(性能下降)不会装箱
null 语义不希望有 null可能为 null
传递参数想传递副本(不改原值)想传递引用(可改原值)
// ✅ 适合 struct:点、颜色、向量等小且不可变的数据
struct Point { public int X, Y; }

// ✅ 适合 class:业务实体
class Order { public int Id; public decimal Total; }

8. 判断一个类型是值还是引用类型

Console.WriteLine(typeof(int).IsValueType);      // True
Console.WriteLine(typeof(string).IsValueType);   // False
Console.WriteLine(typeof(List<int>).IsValueType); // False
Console.WriteLine(typeof(DateTime).IsValueType); // True
Console.WriteLine(typeof(MyStruct).IsValueType); // True(如果是 struct)

// 或者:
// 值类型:int, long, float, double, bool, char, decimal, enum, struct
// 引用类型:string, class, object, dynamic, array, delegate, record

9. 快速参考卡

值类型(数据在栈上)          引用类型(地址在栈上,数据在堆上)
─────────────────────        ─────────────────────────────────
int a = 10;                   Person p = new Person();
int b = a;  ← 复制值           Person p2 = p;  ← 复制地址
b = 20;                       p2.Name = "李四";
a 还是 10 ✅                   p.Name 也变了 ⚠️

struct → 值(适合小数据)       class → 引用(适合大部分业务对象)

五、数组

1. 数组的核心特点

特点说明
类型固定一个数组只能存一种类型
长度固定创建后不能改变大小
索引从0开始第一个元素索引是0
连续内存元素在内存中紧挨着,访问速度快
引用类型数组是引用类型(在堆上分配)

2. 数组的声明和初始化

// 方式1:声明并指定大小,后面赋值
int[] numbers = new int[3];
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;

// 方式2:声明并直接初始化(最常用)
int[] numbers2 = new int[] { 10, 20, 30 };

// 方式3:简化版(语法糖)
int[] numbers3 = { 10, 20, 30 };

// 方式4:先声明,后面再 new
int[] numbers4;
numbers4 = new int[] { 10, 20, 30 };

// 二维数组
int[,] matrix = new int[2, 3] 
{
    { 1, 2, 3 },
    { 4, 5, 6 }
};

// 交错数组(数组的数组)
int[][] jagged = new int[2][];
jagged[0] = new int[] { 1, 2 };
jagged[1] = new int[] { 3, 4, 5 };

3. 数组的默认值

// 数值类型 → 0
int[] intArray = new int[3];
// [0, 0, 0]

// 布尔类型 → false
bool[] boolArray = new bool[3];
// [false, false, false]

// 引用类型 → null
string[] strArray = new string[3];
// [null, null, null]

// 可空类型 → null
int?[] nullableArray = new int?[3];
// [null, null, null]

4. 访问和修改元素

int[] arr = { 10, 20, 30, 40, 50 };

// 读取元素
int first = arr[0];     // 10
int last = arr[4];      // 50

// 修改元素
arr[2] = 100;           // {10, 20, 100, 40, 50}

// 遍历数组
// 方式1:for 循环(可以知道索引)
for (int i = 0; i < arr.Length; i++)
{
    Console.WriteLine($"索引{i}: {arr[i]}");
}

// 方式2:foreach(更简洁,但只读)
foreach (int num in arr)
{
    Console.WriteLine(num);
}

// ⚠️ foreach 中不能修改元素
foreach (int num in arr)
{
    // num = 100;  // ❌ 编译错误
}

5. 数组的重要属性和方法

int[] arr = { 10, 20, 30, 40, 50 };

// Length:数组长度(元素个数)
Console.WriteLine(arr.Length);  // 5

// 排序
Array.Sort(arr);

// 反转
Array.Reverse(arr);

// 查找
int index = Array.IndexOf(arr, 30);  // 返回索引,找不到返回 -1

// 清空(把元素设成默认值)
Array.Clear(arr, 0, arr.Length);  // 全部变 0

// 复制
int[] copy = new int[3];
Array.Copy(arr, copy, 3);  // 复制前3个

// 克隆(创建副本)
int[] clone = (int[])arr.Clone();

// 转换为 List
List<int> list = arr.ToList();

// 字符串连接
string str = string.Join(", ", arr);  // "10, 20, 30, 40, 50"

6. 引用类型特性(重要!)

因为数组是引用类型,赋值时复制的是地址:

int[] arr1 = { 1, 2, 3 };
int[] arr2 = arr1;  // arr2 指向同一个数组

arr2[0] = 100;

Console.WriteLine(arr1[0]);  // 100(变了!)
Console.WriteLine(arr2[0]);  // 100

// 想要真复制,用 Clone()
int[] arr3 = (int[])arr1.Clone();
arr3[0] = 999;
Console.WriteLine(arr1[0]);  // 100(没变)

7. 常见陷阱

陷阱1:索引越界

int[] arr = { 1, 2, 3 };
// arr[3] = 4;  // ❌ IndexOutOfRangeException
// 有效索引:0, 1, 2

陷阱2:用 = 复制引用类型数组

class Person { public string Name; }

Person[] team1 = { new Person { Name = "张三" } };
Person[] team2 = team1;  // 复制的是引用

team2[0].Name = "李四";
Console.WriteLine(team1[0].Name);  // "李四"(被改了)

// 深层复制需要手动处理
Person[] team3 = team1.Select(p => new Person { Name = p.Name }).ToArray();

陷阱3:foreach 中不能修改集合

int[] arr = { 1, 2, 3 };
foreach (var item in arr)
{
    // item = 100;  // ❌ 编译错误
}

// 想修改用 for
for (int i = 0; i < arr.Length; i++)
{
    arr[i] = 100;  // ✅
}

陷阱4:数组长度不可变

int[] arr = { 1, 2, 3 };
// arr.Length = 5;  // ❌ 不能改

// 想扩容只能创建新数组
int[] newArr = new int[5];
Array.Copy(arr, newArr, arr.Length);

8. 多维数组 vs 交错数组

// 二维数组:矩形,每行长度相同
int[,] rect = {
    { 1, 2, 3 },
    { 4, 5, 6 }
};
Console.WriteLine(rect[1, 2]);  // 6(第二行第三列)

// 交错数组:数组的数组,每行长度可以不同
int[][] jagged = new int[2][];
jagged[0] = new int[] { 1, 2 };
jagged[1] = new int[] { 3, 4, 5, 6 };
Console.WriteLine(jagged[1][2]);  // 5

// 性能:通常交错数组更快,但二维数组更直观

9. params 关键字(数组语法糖)

// 定义:可以接收可变数量参数
void PrintNumbers(params int[] numbers)
{
    foreach (int n in numbers)
        Console.Write(n + " ");
}

// 调用:可以直接传多个值
PrintNumbers(1, 2, 3);     // 编译器自动转成数组
PrintNumbers(10, 20);
PrintNumbers();            // 空数组

// 等价于
PrintNumbers(new int[] { 1, 2, 3 });

10. 数组 vs 其他集合

类型长度类型性能适用场景
T[] 数组固定固定最快数据量固定,频繁访问
List<T>可变固定较快需要动态增删
ArrayList可变任意慢(要装箱)已过时,不推荐
Dictionary<K,V>可变键值对查找快需要通过键查找

11. 何时用数组 vs List?

// ✅ 用数组:长度固定,数据量已知
int[] months = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };

// ✅ 用数组:性能敏感的底层操作
byte[] buffer = new byte[1024];  // 网络接收缓冲区

// ✅ 用 List:需要动态增删
List<int> scores = new List<int>();
scores.Add(90);
scores.Add(85);
scores.Remove(90);

// ✅ 用 List:不知道最终数量
List<string> lines = new List<string>();
string line;
while ((line = reader.ReadLine()) != null)
    lines.Add(line);  // 行数未知

快速参考卡

// 创建
int[] arr = new int[3];        // [0, 0, 0]
int[] arr2 = { 1, 2, 3 };      // [1, 2, 3]

// 访问
arr[0] = 10;                   // 赋值
int x = arr[0];                // 读取

// 信息
int len = arr.Length;          // 长度

// 遍历
for (int i = 0; i < arr.Length; i++)
foreach (var item in arr)

// 复制(深 copy)
int[] copy = (int[])arr.Clone();

// 排序
Array.Sort(arr);

六、异常

. 核心概念

概念说明
异常程序运行时发生的意外情况(如除以0、文件不存在)
抛出异常发生错误时,创建并"扔出"一个异常对象
捕获异常try-catch 接住异常,进行处理
try包裹可能出错的代码
catch捕获并处理特定类型的异常
finally无论是否出错,都会执行的代码(如释放资源)
throw手动抛出异常

2. 基本语法

try
{
    // 可能出错的代码
    int result = 10 / 0;  // 会抛出 DivideByZeroException
}
catch (DivideByZeroException ex)
{
    // 专门处理除以0的错误
    Console.WriteLine($"除以0错误:{ex.Message}");
}
catch (Exception ex)
{
    // 捕获其他所有异常(放在最后)
    Console.WriteLine($"其他错误:{ex.Message}");
}
finally
{
    // 一定会执行(不管有没有异常)
    Console.WriteLine("清理工作...");
}

3. 完整的异常处理示例

class Program
{
    static void Main()
    {
        // 示例1:除以0
        try
        {
            int a = 10;
            int b = 0;
            int result = a / b;  // 抛出 DivideByZeroException
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"数学错误:{ex.Message}");
        }
        
        // 示例2:数组越界
        try
        {
            int[] arr = { 1, 2, 3 };
            int x = arr[10];  // 抛出 IndexOutOfRangeException
        }
        catch (IndexOutOfRangeException ex)
        {
            Console.WriteLine($"索引错误:{ex.Message}");
        }
        
        // 示例3:空引用
        try
        {
            string s = null;
            int len = s.Length;  // 抛出 NullReferenceException
        }
        catch (NullReferenceException ex)
        {
            Console.WriteLine($"空引用错误:{ex.Message}");
        }
        
        // 示例4:文件操作
        try
        {
            string content = File.ReadAllText("不存在的文件.txt");
        }
        catch (FileNotFoundException ex)
        {
            Console.WriteLine($"文件不存在:{ex.FileName}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"其他错误:{ex.Message}");
        }
        finally
        {
            Console.WriteLine("无论是否出错,都会执行");
        }
    }
}

4. 常见的异常类型

异常类型触发场景
DivideByZeroException除以0
NullReferenceException访问 null 对象的成员
IndexOutOfRangeException数组索引越界
FormatException格式转换错误(如 int.Parse("abc")
ArgumentException参数不合法
ArgumentNullException参数为 null
FileNotFoundException文件不存在
IOExceptionI/O 错误
InvalidOperationException操作不合法(如遍历时修改集合)
NotImplementedException方法未实现
TimeoutException超时

5. 手动抛出异常(throw)

class BankAccount
{
    private decimal _balance;
    
    public void Withdraw(decimal amount)
    {
        if (amount <= 0)
        {
            throw new ArgumentException("取款金额必须大于0", nameof(amount));
        }
        
        if (amount > _balance)
        {
            throw new InvalidOperationException($"余额不足。当前余额:{_balance}");
        }
        
        _balance -= amount;
    }
}

// 使用
try
{
    var account = new BankAccount();
    account.Withdraw(-100);  // 抛出 ArgumentException
}
catch (ArgumentException ex)
{
    Console.WriteLine($"参数错误:{ex.Message}");
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"操作错误:{ex.Message}");
}

6. 自定义异常

// 定义自定义异常(通常继承 Exception 或 ApplicationException)
public class InsufficientFundsException : Exception
{
    public decimal Balance { get; }
    
    public InsufficientFundsException(decimal balance)
        : base($"余额不足,当前余额:{balance}")
    {
        Balance = balance;
    }
    
    public InsufficientFundsException(decimal balance, string message)
        : base(message)
    {
        Balance = balance;
    }
}

// 使用
class BankAccount
{
    private decimal _balance = 100;
    
    public void Withdraw(decimal amount)
    {
        if (amount > _balance)
        {
            throw new InsufficientFundsException(_balance);
        }
        _balance -= amount;
    }
}

// 捕获自定义异常
try
{
    var account = new BankAccount();
    account.Withdraw(200);
}
catch (InsufficientFundsException ex)
{
    Console.WriteLine($"自定义异常:{ex.Message}");
    Console.WriteLine($"当前余额:{ex.Balance}");
}

7. throw vs throw ex

try
{
    // 某些代码
}
catch (Exception ex)
{
    // ❌ 错误方式:会丢失原始异常堆栈
    throw ex;
    
    // ✅ 正确方式:保留原始堆栈信息
    throw;
}

// 示例:正确保留堆栈
void Method1()
{
    try
    {
        Method2();
    }
    catch (Exception ex)
    {
        // 错误:堆栈会从这里重新开始
        // throw ex;
        
        // 正确:堆栈会包含 Method2 的信息
        throw;
    }
}

void Method2()
{
    throw new Exception("原始错误");
}

8. 异常过滤器(C# 6+)

try
{
    int result = ProcessData();
}
// 只有当条件满足时才捕获
catch (HttpRequestException ex) when (ex.StatusCode == 404)
{
    Console.WriteLine("资源不存在(404)");
}
catch (HttpRequestException ex) when (ex.StatusCode == 500)
{
    Console.WriteLine("服务器错误(500)");
}
catch (Exception ex) when (LogException(ex))  // 先记录再捕获
{
    // 已经记录过了
}

bool LogException(Exception ex)
{
    Console.WriteLine($"记录日志:{ex.Message}");
    return true;  // 返回 true 表示捕获
}

9. finally 的典型用法

// 释放资源
StreamReader reader = null;
try
{
    reader = new StreamReader("file.txt");
    string content = reader.ReadToEnd();
    Console.WriteLine(content);
}
catch (IOException ex)
{
    Console.WriteLine($"读取文件出错:{ex.Message}");
}
finally
{
    // 保证文件一定被关闭
    reader?.Dispose();
}

// 更简洁的方式:using 语句(语法糖)
using (var reader = new StreamReader("file.txt"))
{
    string content = reader.ReadToEnd();
    Console.WriteLine(content);
}
// 离开 using 块时自动调用 Dispose

10. 最佳实践

❌ 不好的做法

// 1. 捕获所有异常但不处理
try { DoSomething(); }
catch (Exception) { }  // 空 catch,吞掉错误

// 2. 捕获一般异常,丢失具体信息
try { DoSomething(); }
catch (Exception ex) { Console.WriteLine("出错了"); }  // 不知道什么错

// 3. 用异常控制正常流程
try { int.Parse(input); }
catch { // 输入不是数字 }  // 应该用 int.TryParse

// 4. 抛出 Exception 基类
throw new Exception("出错了");  // 应该用更具体的异常类型

✅ 好的做法

// 1. 只捕获能处理的异常
try
{
    var data = ParseUserInput(input);
}
catch (FormatException ex)
{
    // 我知道怎么处理格式错误
    Console.WriteLine("输入格式错误,请重新输入");
    return defaultValue;
}
// 其他异常让上层处理

// 2. 使用具体的异常类型
throw new ArgumentException("用户名不能为空", nameof(username));

// 3. 用 Try 模式替代异常
if (int.TryParse(input, out int result))
{
    // 正常处理
}
else
{
    // 处理无效输入
}

// 4. 记录异常信息
catch (Exception ex)
{
    logger.LogError(ex, "处理订单失败,订单ID:{OrderId}", orderId);
    throw;  // 重新抛出,保留堆栈
}

// 5. 验证参数,提前失败
void ProcessOrder(Order order)
{
    if (order == null)
        throw new ArgumentNullException(nameof(order));
    
    if (order.Amount <= 0)
        throw new ArgumentException("金额必须大于0", nameof(order));
    
    // 正常处理...
}

11. 异常的性能影响

// ❌ 慢:抛出异常有开销(几百到几千个CPU周期)
int ParseWithException(string input)
{
    try
    {
        return int.Parse(input);
    }
    catch (FormatException)
    {
        return -1;
    }
}

// ✅ 快:用 Try 模式没有异常开销
int ParseWithTry(string input)
{
    return int.TryParse(input, out int result) ? result : -1;
}

// 结论:异常只用于"异常"情况,不用来作正常流程控制

快速参考卡

// 基本结构
try { /* 可能出错的代码 */ }
catch (SpecificException ex) { /* 处理具体错误 */ }
catch (Exception ex) { /* 处理其他错误 */ }
finally { /* 清理资源(总是执行) */ }

// 手动抛出
throw new ArgumentException("错误信息", "参数名");

// 重新抛出(保留堆栈)
catch (Exception ex) { throw; }

// 条件捕获
catch (Exception ex) when (condition) { }

// 资源释放
using (var resource = new DisposableObject()) { }

七、继承

1. 核心概念

概念说明
基类/父类被继承的类
派生类/子类继承的类
单继承C# 只能继承一个基类
多接口实现可以实现多个接口
base访问父类成员的关键字
protected子类可访问,外部不可访问
override重写父类方法
virtual标记可被子类重写的方法
sealed禁止继承

2. 基本语法

// 基类(父类)
class Animal
{
    public string Name { get; set; }
    
    public void Eat()
    {
        Console.WriteLine($"{Name} 在吃东西");
    }
    
    // virtual: 子类可以重写这个方法
    public virtual void MakeSound()
    {
        Console.WriteLine("动物发出声音");
    }
}

// 派生类(子类)
class Dog : Animal  // 冒号表示继承
{
    // 子类特有的字段
    public string Breed { get; set; }
    
    // 子类特有的方法
    public void WagTail()
    {
        Console.WriteLine($"{Name} 摇尾巴");
    }
    
    // override: 重写父类方法
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} 汪汪叫");
    }
}

// 使用
class Program
{
    static void Main()
    {
        Dog dog = new Dog();
        dog.Name = "旺财";      // 来自父类
        dog.Eat();              // 来自父类
        dog.WagTail();          // 自己新增的
        dog.MakeSound();        // 重写后的:汪汪叫
        
        // 也可以作为父类类型使用
        Animal animal = new Dog();
        animal.MakeSound();     // 运行时调用的是 Dog 的版本(多态)
    }
}

3. 构造函数和 base

class Animal
{
    public string Name { get; set; }
    public int Age { get; set; }
    
    // 无参构造
    public Animal()
    {
        Console.WriteLine("Animal 无参构造");
    }
    
    // 有参构造
    public Animal(string name, int age)
    {
        Name = name;
        Age = age;
        Console.WriteLine($"Animal 构造:{name}, {age}");
    }
}

class Dog : Animal
{
    public string Breed { get; set; }
    
    // 隐式调用父类无参构造
    public Dog()
    {
        Console.WriteLine("Dog 无参构造");
    }
    
    // 用 base 调用父类有参构造
    public Dog(string name, int age, string breed) : base(name, age)
    {
        Breed = breed;
        Console.WriteLine($"Dog 构造:{breed}");
    }
}

// 使用
Dog dog1 = new Dog();
// 输出:
// Animal 无参构造
// Dog 无参构造

Dog dog2 = new Dog("旺财", 3, "金毛");
// 输出:
// Animal 构造:旺财, 3
// Dog 构造:金毛

4. virtual / override vs new

class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Animal 叫");
    }
    
    public void Eat()
    {
        Console.WriteLine("Animal 吃");
    }
}

class Dog : Animal
{
    // override:重写,实现多态
    public override void Speak()
    {
        Console.WriteLine("Dog 汪汪叫");
    }
    
    // new:隐藏父类方法(不是多态)
    public new void Eat()
    {
        Console.WriteLine("Dog 吃骨头");
    }
}

// 测试
Animal a = new Dog();
a.Speak();  // "Dog 汪汪叫"(override:看运行时类型)
a.Eat();    // "Animal 吃"(new:看编译时类型)

Dog d = new Dog();
d.Speak();  // "Dog 汪汪叫"
d.Eat();    // "Dog 吃骨头"

5. 访问修饰符在继承中的影响

class Base
{
    public int PublicField = 1;      // 任何地方都能访问
    private int PrivateField = 2;    // 只有 Base 内部能访问
    protected int ProtectedField = 3; // Base 和子类能访问
    internal int InternalField = 4;   // 同程序集能访问
    protected internal int ProtectedInternal = 5; // 同程序集或子类
    private protected int PrivateProtected = 6;   // 同程序集中的子类
}

class Derived : Base
{
    void Test()
    {
        PublicField = 10;      // ✅
        // PrivateField = 20;  // ❌ 父类私有
        ProtectedField = 30;   // ✅
        InternalField = 40;    // ✅(同程序集)
        ProtectedInternal = 50; // ✅
        PrivateProtected = 60;  // ✅(同程序集)
    }
}

6. 抽象类 abstract

抽象类不能被实例化,只能被继承:

// abstract 修饰的类叫抽象类
abstract class Shape
{
    // 抽象方法:没有实现,子类必须实现
    public abstract double GetArea();
    
    public abstract double GetPerimeter();
    
    // 可以包含普通方法
    public void Display()
    {
        Console.WriteLine($"面积:{GetArea()}");
        Console.WriteLine($"周长:{GetPerimeter()}");
    }
}

class Circle : Shape
{
    public double Radius { get; set; }
    
    public Circle(double radius)
    {
        Radius = radius;
    }
    
    // 必须实现抽象方法(用 override)
    public override double GetArea()
    {
        return Math.PI * Radius * Radius;
    }
    
    public override double GetPerimeter()
    {
        return 2 * Math.PI * Radius;
    }
}

// 使用
// Shape s = new Shape();  // ❌ 不能实例化抽象类
Circle c = new Circle(5);
c.Display();  // 面积:78.54,周长:31.42

7. 完整继承层次示例

// 基类(最高层)
public class Vehicle
{
    public string Brand { get; set; }
    public int Year { get; set; }
    
    public virtual void Start()
    {
        Console.WriteLine("Vehicle 启动");
    }
    
    public virtual void Stop()
    {
        Console.WriteLine("Vehicle 停止");
    }
}

// 第二层
public class Car : Vehicle
{
    public int DoorCount { get; set; }
    
    public override void Start()
    {
        Console.WriteLine("Car 插入钥匙启动");
    }
    
    public void Honk()
    {
        Console.WriteLine("滴滴!");
    }
}

// 第三层
public class ElectricCar : Car
{
    public int BatteryCapacity { get; set; }
    
    public override void Start()
    {
        Console.WriteLine("ElectricCar 按下按钮启动");
    }
    
    public void Charge()
    {
        Console.WriteLine($"充电中,电池容量:{BatteryCapacity} kWh");
    }
}

// 使用
ElectricCar tesla = new ElectricCar();
tesla.Brand = "Tesla";
tesla.DoorCount = 4;
tesla.BatteryCapacity = 75;
tesla.Start();    // ElectricCar 按下按钮启动
tesla.Honk();     // 滴滴!
tesla.Charge();   // 充电中...

8. sealed 封闭类(禁止继承)

// sealed 类不能被继承
sealed class FinalClass
{
    public void Method() { }
}

// class Derived : FinalClass { }  // ❌ 编译错误,不能继承 sealed 类

// sealed 方法:阻止进一步重写
class Parent
{
    public virtual void Method1() { }
    public virtual void Method2() { }
}

class Child : Parent
{
    public override void Method1() { }
    
    // sealed 阻止子类再重写这个方法
    public sealed override void Method2() { }
}

class GrandChild : Child
{
    public override void Method1() { }  // ✅ 可以
    // public override void Method2() { }  // ❌ 不能重写 sealed 方法
}

9. 继承的传递性

class A 
{ 
    public void MethodA() { }
}

class B : A 
{ 
    public void MethodB() { }
}

class C : B 
{ 
    public void MethodC() { }
}

C obj = new C();
obj.MethodA();  // ✅ 间接继承自 A
obj.MethodB();  // ✅ 继承自 B
obj.MethodC();  // ✅ 自己的

10. object 是所有类的基类

// 所有类型最终都继承自 object
class MyClass { }
struct MyStruct { }

// 等价于
class MyClass : object { }

// object 提供的方法
MyClass obj = new MyClass();
Console.WriteLine(obj.ToString());    // 返回类型名称
Console.WriteLine(obj.GetHashCode()); // 返回哈希码
Console.WriteLine(obj.Equals(obj));   // 比较相等性
Console.WriteLine(obj.GetType());     // 获取类型信息

11. 什么时候用继承?

用继承(is-a)用组合(has-a)
Dog is a AnimalCar has a Engine
子类可以替换父类类包含另一个类的实例
需要多态行为只需功能复用
层次结构稳定关系可能变化
// ✅ 继承:is-a 关系
class Employee : Person { }

// ✅ 组合:has-a 关系
class Car
{
    private Engine _engine = new Engine();  // Car has a Engine
}

// ❌ 不要为了代码复用滥用继承
// 错误:Truck is a Engine?不对!
class Truck : Engine { }  // 错误

12. 多重继承的替代:接口

// C# 不支持类的多重继承
// class Child : Parent1, Parent2 { }  // ❌ 不允许

// 但可以实现多个接口
interface IFlyable
{
    void Fly();
}

interface ISwimmable
{
    void Swim();
}

class Duck : Animal, IFlyable, ISwimmable
{
    public void Fly()
    {
        Console.WriteLine("鸭子飞");
    }
    
    public void Swim()
    {
        Console.WriteLine("鸭子游泳");
    }
}

13、virtual 和abstract 的区别

virtualabstract
有无默认实现✅ 有❌ 没有
子类是否必须重写❌ 可选(可以重写,也可以不重写)✅ 必须重写(除非子类也是抽象类)
所在类可以放在普通类或抽象类只能放在抽象类
方法体{ }没有 { },以 ; 结尾
override 关键字子类重写时可选子类重写时必须用

快速参考卡

// 语法
class Derived : Base { }

// 调用父类构造
public Derived() : base() { }
public Derived(int x) : base(x) { }

// 访问父类成员
base.MethodName();
base.FieldName;

// 方法重写
class Base { public virtual void Method() { } }
class Derived : Base { public override void Method() { } }

// 禁止重写
public sealed override void Method() { }

// 禁止继承
sealed class FinalClass { }

// 抽象类
abstract class AbstractClass { public abstract void Method(); }

八、接口

1. 核心概念

概念说明
接口定义一组方法、属性、事件的签名,但没有实现
实现接口类或结构体必须提供接口中所有成员的具体实现
多接口实现一个类可以实现多个接口(弥补单继承的不足)
interface定义接口的关键字
隐式实现直接实现接口成员
显式实现接口名.成员名 的方式实现

2. 基本语法

// 定义接口(习惯以 I 开头)
interface IAnimal
{
    // 接口成员默认是 public,不能加访问修饰符
    void MakeSound();
    string Name { get; set; }
}

// 类实现接口
class Dog : IAnimal
{
    public string Name { get; set; }
    
    public void MakeSound()
    {
        Console.WriteLine($"{Name} 汪汪叫");
    }
}

class Cat : IAnimal
{
    public string Name { get; set; }
    
    public void MakeSound()
    {
        Console.WriteLine($"{Name} 喵喵叫");
    }
}

// 使用
IAnimal dog = new Dog { Name = "旺财" };
IAnimal cat = new Cat { Name = "咪咪" };

dog.MakeSound();  // 旺财 汪汪叫
cat.MakeSound();  // 咪咪 喵喵叫

3. 接口 vs 抽象类

特性接口抽象类
关键字interfaceabstract class
多重实现✅ 可以实现多个❌ 只能继承一个
字段❌ 不能有实例字段✅ 可以有
实现代码❌ 不能有实现(C# 8 之前)✅ 可以有
访问修饰符默认 public,不能加✅ 可以
构造函数❌ 不能✅ 可以
语义"能做什么" (can-do)"是什么" (is-a)
// 什么时候用接口 vs 抽象类?
// 接口:不同种类的事物有相同的行为
interface IFlyable { void Fly(); }
class Bird : IFlyable { public void Fly() { } }
class Airplane : IFlyable { public void Fly() { } }

// 抽象类:同类事物有共同的基类
abstract class Animal { public abstract void Eat(); }
class Dog : Animal { public override void Eat() { } }
class Cat : Animal { public override void Eat() { } }

4. 多个接口实现(弥补单继承)

interface IFlyable
{
    void Fly();
}

interface ISwimmable
{
    void Swim();
}

// 一个类可以实现多个接口
class Duck : IFlyable, ISwimmable
{
    public void Fly()
    {
        Console.WriteLine("鸭子飞");
    }
    
    public void Swim()
    {
        Console.WriteLine("鸭子游泳");
    }
}

// 使用
Duck duck = new Duck();
duck.Fly();   // 鸭子飞
duck.Swim();  // 鸭子游泳

// 作为不同接口使用
IFlyable flyable = duck;
ISwimmable swimmable = duck;
flyable.Fly();
swimmable.Swim();

5. 接口可以继承接口

interface IAnimal
{
    void Eat();
}

interface IMammal : IAnimal
{
    void GiveBirth();
}

class Dog : IMammal
{
    public void Eat()  // 继承自 IAnimal
    {
        Console.WriteLine("狗吃东西");
    }
    
    public void GiveBirth()  // IMammal 自己的
    {
        Console.WriteLine("狗生小狗");
    }
}

// 使用
Dog dog = new Dog();
dog.Eat();         // 狗吃东西
dog.GiveBirth();   // 狗生小狗

IMammal mammal = dog;
mammal.Eat();
mammal.GiveBirth();

IAnimal animal = dog;
animal.Eat();      // 只能调用 Eat

6. 接口中的属性

interface IPerson
{
    // 可以有属性(编译成 get/set 方法)
    string Name { get; set; }
    int Age { get; }
    string FullName { get; }  // 只读属性
}

class Student : IPerson
{
    private string _name;
    private int _age;
    
    public string Name
    {
        get => _name;
        set => _name = value;
    }
    
    public int Age => _age;  // 只读,只能在构造函数赋值
    
    public string FullName => $"学生:{_name}";
    
    public Student(string name, int age)
    {
        _name = name;
        _age = age;
    }
}

7. 显式接口实现(解决命名冲突)

interface IFile
{
    void Save();
}

interface IDatabase
{
    void Save();
}

class DataService : IFile, IDatabase
{
    // 隐式实现
    public void Save()
    {
        Console.WriteLine("默认实现");
    }
    
    // 显式实现 IFile 的 Save
    void IFile.Save()
    {
        Console.WriteLine("保存到文件");
    }
    
    // 显式实现 IDatabase 的 Save
    void IDatabase.Save()
    {
        Console.WriteLine("保存到数据库");
    }
}

// 使用
DataService service = new DataService();
service.Save();              // 默认实现

IFile file = service;
file.Save();                // 保存到文件

IDatabase db = service;
db.Save();                  // 保存到数据库

8. 接口作为参数和返回值

interface ILogger
{
    void Log(string message);
}

class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"[Console] {message}");
    }
}

class FileLogger : ILogger
{
    public void Log(string message)
    {
        // 写入文件
        File.AppendAllText("log.txt", message + "\n");
    }
}

// 方法接受接口类型
void ProcessData(ILogger logger)
{
    logger.Log("开始处理数据");
    // ... 业务逻辑
    logger.Log("处理完成");
}

// 使用
ProcessData(new ConsoleLogger());
ProcessData(new FileLogger());

9. 接口的默认实现(C# 8.0+)

// C# 8.0 之后,接口可以有默认实现
interface IShape
{
    // 抽象方法(必须实现)
    double GetArea();
    
    // 默认实现(可选实现)
    void Display()
    {
        Console.WriteLine($"面积:{GetArea()}");
    }
    
    // 静态成员
    static string TypeName = "形状";
    
    static void ShowType()
    {
        Console.WriteLine(TypeName);
    }
}

class Circle : IShape
{
    public double Radius { get; set; }
    
    public double GetArea()
    {
        return Math.PI * Radius * Radius;
    }
    
    // 可以不实现 Display,使用默认的
}

// 使用
Circle circle = new Circle { Radius = 5 };
circle.Display();  // 使用默认实现

10. 常用内置接口

// 1. IDisposable:资源释放
class ResourceHolder : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("释放资源");
    }
}

// 2. IEnumerable:可遍历
class MyCollection : IEnumerable<int>
{
    private int[] _data = { 1, 2, 3 };
    
    public IEnumerator<int> GetEnumerator()
    {
        foreach (int item in _data)
            yield return item;
    }
    
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

// 3. IComparable:可比较
class Person : IComparable<Person>
{
    public string Name { get; set; }
    public int Age { get; set; }
    
    public int CompareTo(Person other)
    {
        return this.Age.CompareTo(other.Age);  // 按年龄排序
    }
}

// 4. IEquatable:可判断相等
class Product : IEquatable<Product>
{
    public int Id { get; set; }
    
    public bool Equals(Product other)
    {
        if (other == null) return false;
        return this.Id == other.Id;
    }
}

11. 完整示例:支付系统

// 定义支付接口
interface IPayment
{
    void Pay(decimal amount);
    bool Validate();
    string PaymentMethod { get; }
}

// 信用卡支付
class CreditCardPayment : IPayment
{
    public string PaymentMethod => "信用卡";
    
    public void Pay(decimal amount)
    {
        Console.WriteLine($"使用信用卡支付 {amount:C}");
    }
    
    public bool Validate()
    {
        Console.WriteLine("验证信用卡信息...");
        return true;
    }
}

// 支付宝支付
class AlipayPayment : IPayment
{
    public string PaymentMethod => "支付宝";
    
    public void Pay(decimal amount)
    {
        Console.WriteLine($"使用支付宝支付 {amount:C}");
    }
    
    public bool Validate()
    {
        Console.WriteLine("验证支付宝账户...");
        return true;
    }
}

// 微信支付
class WeChatPayment : IPayment
{
    public string PaymentMethod => "微信";
    
    public void Pay(decimal amount)
    {
        Console.WriteLine($"使用微信支付 {amount:C}");
    }
    
    public bool Validate()
    {
        Console.WriteLine("验证微信账户...");
        return true;
    }
}

// 订单类
class Order
{
    public decimal Amount { get; set; }
    
    public void Checkout(IPayment payment)
    {
        if (payment.Validate())
        {
            payment.Pay(Amount);
            Console.WriteLine($"支付成功,方式:{payment.PaymentMethod}");
        }
        else
        {
            Console.WriteLine("支付失败");
        }
    }
}

// 使用
Order order = new Order { Amount = 99.99m };
order.Checkout(new CreditCardPayment());
order.Checkout(new AlipayPayment());
order.Checkout(new WeChatPayment());

// 运行时动态选择
string userChoice = "支付宝";
IPayment selectedPayment = userChoice switch
{
    "支付宝" => new AlipayPayment(),
    "微信" => new WeChatPayment(),
    _ => new CreditCardPayment()
};
order.Checkout(selectedPayment);

12. 接口 vs 抽象类 vs 类

特性接口抽象类普通类
实例化
继承/实现数量多个一个一个
成员实现默认实现(C# 8+)可以有必须有
字段
构造函数
访问修饰符默认 public各种各种

快速参考卡

// 定义接口
interface IMyInterface
{
    void Method1();
    int Property { get; set; }
}

// 实现接口
class MyClass : IMyInterface
{
    public void Method1() { }
    public int Property { get; set; }
}

// 多个接口
class Multi : IInterface1, IInterface2 { }

// 接口继承接口
interface IDerived : IBase { }

// 显式实现
void IInterface.Method() { }

// 作为类型使用
IMyInterface obj = new MyClass();

九、枚举

1. 核心概念

概念说明
枚举定义一组相关的命名常量
底层类型默认是 int,可指定为 byteshortlong 等整数类型
默认从 0 开始递增,可以手动指定
用途表示状态、选项、类别等有限集合

2. 基本语法

// 定义一个枚举
enum Weekday
{
    Monday,     // 默认值 0
    Tuesday,    // 1
    Wednesday,  // 2
    Thursday,   // 3
    Friday,     // 4
    Saturday,   // 5
    Sunday      // 6
}

// 使用枚举
class Program
{
    static void Main()
    {
        // 声明变量
        Weekday today = Weekday.Monday;
        
        // 打印
        Console.WriteLine(today);           // Monday
        Console.WriteLine((int)today);      // 0
        
        // 比较
        if (today == Weekday.Monday)
        {
            Console.WriteLine("周一,开始工作");
        }
        
        // switch 中使用(经典用法)
        switch (today)
        {
            case Weekday.Saturday:
            case Weekday.Sunday:
                Console.WriteLine("周末");
                break;
            default:
                Console.WriteLine("工作日");
                break;
        }
    }
}

3. 指定值和底层类型

// 手动指定值
enum StatusCode
{
    Success = 200,
    NotFound = 404,
    ServerError = 500
}

// 指定底层类型(默认 int)
enum Priority : byte  // 可以节省内存
{
    Low = 1,
    Medium = 5,
    High = 10
}

// 可以有重复值
enum ErrorCode
{
    None = 0,
    Unknown = -1,
    NetworkError = 100,
    Timeout = 100   // 允许重复
}

// 使用
Console.WriteLine((int)StatusCode.Success);     // 200
Console.WriteLine((byte)Priority.High);         // 10

4. 枚举作为标志位 [Flags]

用于表示可以组合的选项:

// 使用 Flags 特性,通常配合 2 的幂
[Flags]
enum FileAccess
{
    None = 0,      // 0b0000
    Read = 1,      // 0b0001
    Write = 2,     // 0b0010
    Execute = 4,   // 0b0100
    Delete = 8     // 0b1000
}

class Program
{
    static void Main()
    {
        // 组合权限(按位或)
        FileAccess access = FileAccess.Read | FileAccess.Write;
        
        Console.WriteLine(access);  // Read, Write
        
        // 检查是否有某权限(按位与)
        bool canRead = (access & FileAccess.Read) == FileAccess.Read;    // True
        bool canDelete = access.HasFlag(FileAccess.Delete);              // False
        
        // 添加权限
        access |= FileAccess.Execute;
        Console.WriteLine(access);  // Read, Write, Execute
        
        // 移除权限
        access &= ~FileAccess.Write;
        Console.WriteLine(access);  // Read, Execute
        
        Console.WriteLine($"二进制: {Convert.ToString((int)access, 2)}");  // 101 (5)
    }
}

十、ref和out

1. 一句话核心区别

refout
传入前必须初始化✅ 是❌ 否
方法内必须赋值❌ 否✅ 是
用途修改现有变量返回多个值

2. 代码直接对比

// ref 示例
int x = 10;        // ✅ 必须提前赋值
AddOne(ref x);
Console.WriteLine(x);  // 11

void AddOne(ref int num)
{
    num++;  // 修改传入的变量
}

// out 示例
int y;             // ✅ 不需要赋值(未初始化)
SetToTen(out y);
Console.WriteLine(y);  // 10

void SetToTen(out int num)
{
    num = 10;      // ✅ 必须赋值
}

3. 详细说明

ref:传入前必须初始化

// ✅ 正确:传入前已赋值
int a = 5;
Modify(ref a);

void Modify(ref int value)
{
    value = value * 2;
}

// ❌ 错误:传入前未初始化
int b;
Modify(ref b);  // 编译错误:b 未初始化

out:方法内必须赋值

// ✅ 正确:方法内赋值了
int result;
TryParse("123", out result);

bool TryParse(string input, out int output)
{
    if (int.TryParse(input, out output))  // output 被赋值
    {
        return true;
    }
    output = 0;  // 即使失败也要赋值
    return false;
}

// ❌ 错误:方法内没赋值
void BadMethod(out int value)
{
    // 没有给 value 赋值
}  // 编译错误:必须给 out 参数赋值

4. 典型使用场景

场景1:out 用于返回多个值(TryParse 模式)

// C# 内置的 TryParse 模式
if (int.TryParse("123", out int result))
{
    Console.WriteLine($"转换成功:{result}");
}
else
{
    Console.WriteLine("转换失败");
}

// 自定义返回多个值
bool GetUserById(int id, out string name, out int age)
{
    if (id == 1)
    {
        name = "张三";
        age = 25;
        return true;
    }
    name = null;
    age = 0;
    return false;
}

// 使用
if (GetUserById(1, out string userName, out int userAge))
{
    Console.WriteLine($"找到用户:{userName}{userAge}岁");
}

场景2:ref 用于交换值

void Swap(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 5, y = 10;
Swap(ref x, ref y);
Console.WriteLine($"x={x}, y={y}");  // x=10, y=5

场景3:ref 用于性能优化(大结构体)

struct LargeStruct
{
    public long Data1, Data2, Data3, Data4, Data5;
    // ... 很多字段
}

void Process(ref LargeStruct data)  // 传引用,避免复制 40 字节
{
    data.Data1 = 100;
}

LargeStruct data = new LargeStruct();
Process(ref data);  // 复制的是地址,不是整个结构

场景4:out 用于获取多个计算结果

void CalculateStatistics(int[] numbers, out int sum, out double average)
{
    sum = 0;
    foreach (int n in numbers)
        sum += n;
    
    average = numbers.Length > 0 ? (double)sum / numbers.Length : 0;
}

// 使用
int[] scores = { 85, 90, 78, 92 };
CalculateStatistics(scores, out int total, out double avg);
Console.WriteLine($"总分:{total},平均分:{avg:F2}");

5. 对比:普通传值 vs ref vs out

// 普通传值(复制参数)
void ByValue(int x)
{
    x = 100;
}

// ref(传引用)
void ByRef(ref int x)
{
    x = 100;
}

// out(传引用,出口必须赋值)
void ByOut(out int x)
{
    x = 100;  // 必须赋值
}

// 测试
int a = 10;
int b = 10;
int c;  // 未初始化

ByValue(a);
Console.WriteLine(a);  // 10(没变)

ByRef(ref b);
Console.WriteLine(b);  // 100(变了)

ByOut(out c);
Console.WriteLine(c);  // 100(变了)

6. 引用类型参数传递

很多人困惑:引用类型(如 class)本身是引用,还需要 ref 吗?

class Person
{
    public string Name { get; set; }
}

// 普通传参:可以修改对象内容,但不能替换整个对象
void ChangeName(Person p)
{
    p.Name = "李四";  // ✅ 影响外部
    p = new Person { Name = "王五" };  // ❌ 不影响外部
}

// ref 传参:可以替换整个对象
void ChangePerson(ref Person p)
{
    p.Name = "李四";   // ✅ 影响外部
    p = new Person { Name = "王五" };  // ✅ 影响外部
}

// 使用
Person p1 = new Person { Name = "张三" };
ChangeName(p1);
Console.WriteLine(p1.Name);  // 李四(内容被改了)

Person p2 = new Person { Name = "张三" };
ChangePerson(ref p2);
Console.WriteLine(p2.Name);  // 王五(整个对象被替换了)

总结

  • 不加 ref:能修改对象的内容,不能替换对象本身
  • ref:既能修改内容,也能替换整个对象

7. out 参数的变量声明(C# 7+)

// 旧语法
int oldResult;
int.TryParse("123", out oldResult);

// C# 7+ 新语法:内联声明
int.TryParse("123", out int newResult);

// 甚至可以用 var
int.TryParse("123", out var result);

// 忽略不需要的 out 参数
int.TryParse("123", out _);  // 用 _ 丢弃

十一、struct和class

实际规则:什么时候用什么?

// ✅ 用 struct:小且不可变
struct Point { public int X, Y; }        // 8字节 ✅
struct Vector3 { public float X, Y, Z; } // 12字节 ✅
struct Complex { public double Real, Imag; } // 16字节 ✅ 边界

// ❌ 不用 struct:太大
struct BigData 
{ 
    public int A1, A2, A3, A4, A5, A6, A7, A8, A9, A10;  // 40字节 ❌
    // 用 class 更好
}

// ❌ 不用 struct:频繁修改
struct MutablePoint { public int X, Y; }
List<MutablePoint> points = ...;
points[0].X = 10;  // 需要取出改再放回,麻烦且慢
// 用 class 更好

// ✅ 用 class:大对象
class Player { public string Name; public int Score; public float X, Y, Z; }

// ✅ 用 class:需要继承
class Animal { }
class Dog : Animal { }

// ✅ 用 struct:数据容器(DTO)
struct Config { public int MaxRetry; public float Timeout; }

"官方"指导原则(Microsoft)

微软推荐的 struct 使用条件(全部满足才用 struct):

  1. 逻辑上是一个值(像 int、double 一样)
  2. 实例很小(≤ 16 字节)
  3. 不可变(创建后不修改)
  4. 不需要频繁装箱
// ✅ 完美符合:点、颜色、复数
struct Point { public int X { get; } public int Y { get; } }

// ❌ 不符合条件:应该用 class
class Customer { public string Name; public int Age; }
// 理由:
// - 不是值语义(Customer 是实体)
// - 很大(string 引用 + int)
// - 会修改(年龄会变)

性能实测数据

using System.Diagnostics;

// 小结构体(8字节)
struct SmallStruct { public int X, Y; }
class SmallClass { public int X, Y; }

// 大结构体(40字节)
struct LargeStruct { public int A, B, C, D, E, F, G, H, I, J; }
class LargeClass { public int A, B, C, D, E, F, G, H, I, J; }

// 测试创建 1000 万个对象
// 结果:
// SmallStruct 数组:~50ms,0 GC
// SmallClass 数组:~300ms,大量 GC

// 测试传递 1000 万次
// 传 SmallStruct:~100ms(复制8字节)
// 传 SmallClass:~50ms(传引用8字节,多一次间接访问)
// 传 LargeStruct:~400ms(复制40字节 ❌)
// 传 LargeClass:~50ms(传引用8字节 ✅)

你的理解修正版

你说的实际情况
"结构体定义对象里有什么字段"✅ 正确
"class 也可以"✅ 正确
"class 性能更差"⚠️ 不一定:小对象 struct 快,大对象 class 快

一句话总结

小对象(≤16字节)且逻辑上是"值"的用 struct(性能好);大对象或逻辑上是"实体"的用 class(性能好)。不是 struct 永远比 class 快,选错了反而更慢。

就像是:自行车在城市短途比汽车快(结构体在小数据场景),但长途运输肯定用汽车(类在大数据场景)。没有绝对的好坏,只有是否合适。

十二、委托

1. 一句话核心

概念说明
委托指向方法的引用类型
作用把方法当作参数传递、延迟执行
声明delegate 返回值类型 委托名(参数);
本质继承自 System.Delegate 的类

2. 基本语法

// 1. 声明委托(定义方法签名)
delegate void PrintDelegate(string message);
delegate int MathDelegate(int a, int b);

// 2. 创建委托实例,指向具体方法
PrintDelegate print = Console.WriteLine;
MathDelegate add = Add;

// 3. 调用委托
print("Hello");   // 输出 Hello
int result = add(3, 5);  // 8

// 方法定义(与委托签名匹配)
int Add(int a, int b) => a + b;

3. 委托的四种使用方式

class Program
{
    static void Main()
    {
        // 方式1:指向静态方法
        PrintDelegate d1 = PrintToConsole;
        d1("Hello 1");
        
        // 方式2:指向实例方法
        var logger = new Logger();
        PrintDelegate d2 = logger.PrintToFile;
        d2("Hello 2");
        
        // 方式3:匿名方法(C# 2.0)
        PrintDelegate d3 = delegate(string msg)
        {
            Console.WriteLine($"匿名方法:{msg}");
        };
        d3("Hello 3");
        
        // 方式4:Lambda 表达式(C# 3.0+,最常用)
        PrintDelegate d4 = (string msg) => Console.WriteLine($"Lambda:{msg}");
        d4("Hello 4");
        // 更简洁
        PrintDelegate d5 = msg => Console.WriteLine(msg);
    }
    
    delegate void PrintDelegate(string message);
    
    static void PrintToConsole(string msg) => Console.WriteLine(msg);
    
    class Logger
    {
        public void PrintToFile(string msg) => Console.WriteLine($"[File] {msg}");
    }
}

4. 委托作为参数(回调)

class Calculator
{
    // 委托参数:允许调用方传入计算方法
    public int Calculate(int a, int b, MathDelegate operation)
    {
        return operation(a, b);
    }
}

delegate int MathDelegate(int x, int y);

// 使用
var calc = new Calculator();

// 传入不同算法
int sum = calc.Calculate(10, 5, Add);        // 15
int diff = calc.Calculate(10, 5, Subtract);  // 5
int product = calc.Calculate(10, 5, Multiply); // 50

int Add(int a, int b) => a + b;
int Subtract(int a, int b) => a - b;
int Multiply(int a, int b) => a * b;

5. 多播委托(一个委托执行多个方法)

delegate void NotifyDelegate(string message);

void SendEmail(string msg) => Console.WriteLine($"发送邮件:{msg}");
void SendSMS(string msg) => Console.WriteLine($"发送短信:{msg}");
void LogToFile(string msg) => Console.WriteLine($"记录日志:{msg}");

// 组合委托
NotifyDelegate notify = SendEmail;
notify += SendSMS;      // 添加方法
notify += LogToFile;    // 添加

notify("系统更新通知");
// 输出:
// 发送邮件:系统更新通知
// 发送短信:系统更新通知
// 记录日志:系统更新通知

// 移除方法
notify -= SendEmail;
notify("再次通知");
// 输出:
// 发送短信:再次通知
// 记录日志:再次通知

6. 内置委托:ActionFuncPredicate

C# 提供常用委托,无需自己声明:

// Action:无返回值(void)
Action print = () => Console.WriteLine("Hello");
Action<string> printMsg = msg => Console.WriteLine(msg);
Action<int, int> printSum = (a, b) => Console.WriteLine(a + b);

// Func:有返回值(最后一个泛型是返回值)
Func<int> getRandom = () => new Random().Next();
Func<int, int, int> add = (a, b) => a + b;
Func<string, int> getLength = s => s.Length;

// Predicate:返回 bool(等同于 Func<T, bool>)
Predicate<int> isPositive = x => x > 0;
Predicate<string> isEmpty = s => string.IsNullOrEmpty(s);

// 使用
int result = add(3, 5);        // 8
int len = getLength("Hello");   // 5
bool positive = isPositive(10); // true

7. 实际应用场景

场景1:事件处理( GUI、Web)

Button button = new Button();
button.Click += ButtonClicked;      // 委托作为事件处理器

void ButtonClicked(object sender, EventArgs e)
{
    Console.WriteLine("按钮被点击");
}

场景2:LINQ(最常用)

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

// Where 接受 Func<int, bool> 委托
var evens = numbers.Where(n => n % 2 == 0);  // 2,4,6

// Select 接受 Func<int, string> 委托
var strings = numbers.Select(n => $"数字:{n}");

// OrderBy 接受 Func<int, int> 委托(提取排序键)
var sorted = numbers.OrderBy(n => -n);  // 6,5,4,3,2,1

场景3:异步回调

void DownloadFile(string url, Action<bool, string> callback)
{
    // 模拟下载
    Thread.Sleep(1000);
    
    bool success = true;
    string result = "文件内容";
    
    callback(success, result);
}

// 使用
DownloadFile("http://example.com", (success, data) =>
{
    if (success)
        Console.WriteLine($"下载成功:{data}");
    else
        Console.WriteLine("下载失败");
});

场景4:排序和比较

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

List<Person> people = new List<Person>
{
    new Person { Name = "张三", Age = 30 },
    new Person { Name = "李四", Age = 25 },
    new Person { Name = "王五", Age = 35 }
};

// 使用自定义比较委托
people.Sort((p1, p2) => p1.Age.CompareTo(p2.Age));  // 按年龄排序
people.Sort((p1, p2) => p1.Name.CompareTo(p2.Name)); // 按姓名排序

8. Func 和 Action 的更多示例

// Action 各种形式
Action a1 = () => Console.WriteLine("无参数");
Action<int> a2 = x => Console.WriteLine(x);
Action<int, int> a3 = (x, y) => Console.WriteLine(x + y);
Action<int, int, int> a4 = (x, y, z) => Console.WriteLine(x + y + z);

// Func 各种形式
Func<int> f1 = () => 42;
Func<int, int> f2 = x => x * 2;
Func<int, int, int> f3 = (x, y) => x + y;
Func<int, int, int, int> f4 = (x, y, z) => x + y + z;

// 实际例子:缓存计算结果
Dictionary<string, Func<int, int, int>> operations = new()
{
    ["+"] = (a, b) => a + b,
    ["-"] = (a, b) => a - b,
    ["*"] = (a, b) => a * b,
    ["/"] = (a, b) => a / b
};

int result = operations["+"](10, 5);  // 15

9. 委托与接口的对比

// 方案1:用委托
class Processor
{
    public void Process(int data, Func<int, int> transform)
    {
        int result = transform(data);
        Console.WriteLine(result);
    }
}

// 方案2:用接口
interface ITransformer
{
    int Transform(int data);
}

class Processor2
{
    public void Process(int data, ITransformer transformer)
    {
        int result = transformer.Transform(data);
        Console.WriteLine(result);
    }
}

// 使用对比
var processor = new Processor();
processor.Process(5, x => x * 2);  // 委托:简洁

var processor2 = new Processor2();
processor2.Process(5, new DoubleTransformer());  // 接口:需要单独定义类

class DoubleTransformer : ITransformer
{
    public int Transform(int data) => data * 2;
}

10. 常见陷阱

陷阱1:多播委托的返回值

Func<int> getNumber = () => 1;
getNumber += () => 2;
getNumber += () => 3;

int result = getNumber();  // 返回 3(最后一个)
// 前面的返回值被丢弃了

// 如果需要所有返回值,自己调用
foreach (Func<int> d in getNumber.GetInvocationList())
{
    Console.WriteLine(d());  // 1,2,3
}

陷阱2:捕获变量

List<Action> actions = new List<Action>();

for (int i = 0; i < 5; i++)
{
    actions.Add(() => Console.WriteLine(i));  // 捕获同一个 i
}

foreach (var action in actions)
{
    action();  // 输出 5 个 5,不是 0,1,2,3,4
}

// 修正:复制局部变量
for (int i = 0; i < 5; i++)
{
    int temp = i;
    actions.Add(() => Console.WriteLine(temp));  // 捕获不同的 temp
}

快速参考卡

// 声明
delegate void MyDel(int x);  // 传统
Action<int> myAction;         // 无返回值
Func<int, string> myFunc;     // 有返回值

// 赋值
MyDel d1 = MyMethod;
MyDel d2 = x => Console.WriteLine(x);
MyDel d3 = delegate(int x) { Console.WriteLine(x); };

// 组合
d += AnotherMethod;   // 添加
d -= AnotherMethod;   // 移除

// 调用
d(10);
int result = myFunc(5);

// 内置委托
Action a;           // void()
Action<int> a1;     // void(int)
Func<int> f;        // int()
Func<int,string> f1; // string(int)
Predicate<int> p;   // bool(int)

十三、事件

. 一句话核心

概念说明
事件封装后的委托,只能从声明它的类内部调用
发布者触发事件的类
订阅者响应事件的类
关键字event
本质委托的一个实例,加了访问限制

2. 基本语法

// 1. 声明委托(定义事件签名)
public delegate void MyEventHandler(object sender, EventArgs e);

// 2. 声明事件(基于委托)
public event MyEventHandler MyEvent;

// 3. 触发事件(只能在声明事件的类内部)
protected virtual void OnMyEvent()
{
    MyEvent?.Invoke(this, EventArgs.Empty);
}

3. 完整示例

// 发布者:按钮
class Button
{
    // 声明事件(使用内置的 EventHandler)
    public event EventHandler Click;
    
    // 点击方法(模拟用户点击)
    public void OnClick()
    {
        Console.WriteLine("按钮被点击了");
        
        // 触发事件(注意检查 null)
        Click?.Invoke(this, EventArgs.Empty);
    }
}

// 订阅者:窗体
class Form
{
    public Form()
    {
        Button btn = new Button();
        
        // 订阅事件
        btn.Click += Button_Click;
        
        // 模拟点击
        btn.OnClick();
    }
    
    // 事件处理方法(必须匹配委托签名)
    private void Button_Click(object sender, EventArgs e)
    {
        Console.WriteLine("窗体收到点击事件,正在处理...");
    }
}

// 使用
new Form();  // 输出:
// 按钮被点击了
// 窗体收到点击事件,正在处理...

4. 自定义事件参数

// 自定义事件参数(通常继承 EventArgs)
class TemperatureEventArgs : EventArgs
{
    public double Temperature { get; set; }
    public DateTime Time { get; set; }
}

// 温度传感器(发布者)
class TemperatureSensor
{
    // 声明事件
    public event EventHandler<TemperatureEventArgs> TemperatureChanged;
    
    private double _currentTemp;
    
    public void UpdateTemperature(double newTemp)
    {
        _currentTemp = newTemp;
        
        // 温度变化时触发事件
        TemperatureChanged?.Invoke(this, new TemperatureEventArgs
        {
            Temperature = newTemp,
            Time = DateTime.Now
        });
    }
}

// 显示器(订阅者)
class TemperatureDisplay
{
    public TemperatureDisplay(TemperatureSensor sensor)
    {
        // 订阅事件
        sensor.TemperatureChanged += OnTemperatureChanged;
    }
    
    private void OnTemperatureChanged(object sender, TemperatureEventArgs e)
    {
        Console.WriteLine($"[{e.Time}] 温度更新为:{e.Temperature}°C");
    }
}

// 报警器(另一个订阅者)
class Alarm
{
    public Alarm(TemperatureSensor sensor)
    {
        sensor.TemperatureChanged += OnTemperatureChanged;
    }
    
    private void OnTemperatureChanged(object sender, TemperatureEventArgs e)
    {
        if (e.Temperature > 30)
        {
            Console.WriteLine($"⚠️ 高温警报!{e.Temperature}°C");
        }
    }
}

// 使用
var sensor = new TemperatureSensor();
new TemperatureDisplay(sensor);
new Alarm(sensor);

sensor.UpdateTemperature(25);  // 正常温度
sensor.UpdateTemperature(35);  // 触发警报
// 输出:
// [14:30:00] 温度更新为:25°C
// [14:30:01] 温度更新为:35°C
// ⚠️ 高温警报!35°C

5. 事件 vs 委托

class Test
{
    // 委托字段:可以在外部直接调用
    public Action MyDelegate;
    
    // 事件:外部只能订阅/取消,不能直接调用
    public event Action MyEvent;
    
    public void TestMethod()
    {
        // 委托:可以在外部直接调用
        MyDelegate?.Invoke();  // 外部可以 test.MyDelegate()
        
        // 事件:只能在内部调用
        MyEvent?.Invoke();     // 外部不能 test.MyEvent()
    }
}

// 使用
Test test = new Test();

// 委托:
test.MyDelegate = () => Console.WriteLine("Hello");  // ✅ 可以赋值
test.MyDelegate();  // ✅ 可以直接调用
test.MyDelegate = null;  // ✅ 可以清空

// 事件:
test.MyEvent += () => Console.WriteLine("Hello");  // ✅ 订阅
test.MyEvent -= () => Console.WriteLine("Hello");  // ✅ 取消订阅
// test.MyEvent = () => Console.WriteLine("Hello");  // ❌ 不能直接赋值
// test.MyEvent();  // ❌ 不能直接调用
// test.MyEvent = null;  // ❌ 不能清空

6. 标准事件模式(.NET 规范)

// 1. 事件参数通常继承 EventArgs
public class FileProcessedEventArgs : EventArgs
{
    public string FileName { get; set; }
    public long FileSize { get; set; }
}

// 2. 发布者
public class FileProcessor
{
    // 声明事件(使用 EventHandler<T>)
    public event EventHandler<FileProcessedEventArgs> FileProcessed;
    
    // 受保护的触发方法(虚方法,允许子类重写)
    protected virtual void OnFileProcessed(string fileName, long fileSize)
    {
        FileProcessed?.Invoke(this, new FileProcessedEventArgs
        {
            FileName = fileName,
            FileSize = fileSize
        });
    }
    
    public void ProcessFile(string fileName)
    {
        // 处理文件...
        Console.WriteLine($"正在处理:{fileName}");
        
        // 触发事件
        OnFileProcessed(fileName, 1024);
    }
}

// 3. 订阅者
class FileMonitor
{
    public FileMonitor(FileProcessor processor)
    {
        processor.FileProcessed += OnFileProcessed;
    }
    
    private void OnFileProcessed(object sender, FileProcessedEventArgs e)
    {
        Console.WriteLine($"文件处理完成:{e.FileName},大小:{e.FileSize} 字节");
    }
}

7. 实际应用场景

场景1:UI 控件事件

// 模拟 Windows Forms 或 WPF 按钮
class Button
{
    // 鼠标相关事件
    public event EventHandler Click;
    public event EventHandler MouseEnter;
    public event EventHandler MouseLeave;
    
    public void SimulateClick()
    {
        Console.WriteLine("用户点击了按钮");
        Click?.Invoke(this, EventArgs.Empty);
    }
    
    public void SimulateMouseEnter()
    {
        MouseEnter?.Invoke(this, EventArgs.Empty);
    }
}

// 使用
Button btn = new Button();
btn.Click += (s, e) => Console.WriteLine("执行点击逻辑");
btn.MouseEnter += (s, e) => Console.WriteLine("鼠标进入区域");

btn.SimulateClick();
btn.SimulateMouseEnter();

场景2:进度报告

class Downloader
{
    // 进度事件
    public event EventHandler<int> ProgressChanged;
    public event EventHandler DownloadCompleted;
    public event EventHandler<Exception> DownloadFailed;
    
    public async Task DownloadAsync(string url)
    {
        try
        {
            for (int i = 0; i <= 100; i += 10)
            {
                await Task.Delay(100);
                ProgressChanged?.Invoke(this, i);
            }
            
            DownloadCompleted?.Invoke(this, EventArgs.Empty);
        }
        catch (Exception ex)
        {
            DownloadFailed?.Invoke(this, ex);
        }
    }
}

// 使用
var downloader = new Downloader();
downloader.ProgressChanged += (s, p) => Console.WriteLine($"进度:{p}%");
downloader.DownloadCompleted += (s, e) => Console.WriteLine("下载完成");
downloader.DownloadFailed += (s, ex) => Console.WriteLine($"失败:{ex.Message}");

await downloader.DownloadAsync("http://example.com/file.zip");

场景3:数据变更通知

class ObservablePerson
{
    private string _name;
    private int _age;
    
    // 属性变更事件
    public event EventHandler<PropertyChangedEventArgs> PropertyChanged;
    
    public string Name
    {
        get => _name;
        set
        {
            if (_name != value)
            {
                _name = value;
                OnPropertyChanged(nameof(Name));
            }
        }
    }
    
    public int Age
    {
        get => _age;
        set
        {
            if (_age != value)
            {
                _age = value;
                OnPropertyChanged(nameof(Age));
            }
        }
    }
    
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

class PropertyChangedEventArgs : EventArgs
{
    public string PropertyName { get; }
    public PropertyChangedEventArgs(string name) => PropertyName = name;
}

// 使用
var person = new ObservablePerson();
person.PropertyChanged += (s, e) => Console.WriteLine($"属性改变:{e.PropertyName}");

person.Name = "张三";  // 输出:属性改变:Name
person.Age = 25;       // 输出:属性改变:Age

8. 弱事件模式(避免内存泄漏)

// 普通事件可能导致内存泄漏(订阅者没取消订阅)
class Publisher
{
    public event EventHandler DataArrived;
}

class Subscriber
{
    public Subscriber(Publisher publisher)
    {
        publisher.DataArrived += OnDataArrived;  // publisher 持有 subscriber 引用
    }
    
    private void OnDataArrived(object sender, EventArgs e) { }
    // 如果忘记 -=,Subscriber 永远不会被 GC
}

// 使用 WeakEventManager(.NET 4.5+)
class WeakEventSubscriber
{
    public WeakEventSubscriber(Publisher publisher)
    {
        WeakEventManager<Publisher, EventArgs>
            .AddHandler(publisher, nameof(Publisher.DataArrived), OnDataArrived);
    }
    
    private void OnDataArrived(object sender, EventArgs e) { }
    // 不需要手动取消订阅
}

9. 常见陷阱

陷阱1:事件在触发时为 null

// ❌ 错误:可能为 null
public void Trigger()
{
    MyEvent(this, EventArgs.Empty);  // 如果没有订阅,NullReferenceException
}

// ✅ 正确:检查 null
public void Trigger()
{
    MyEvent?.Invoke(this, EventArgs.Empty);
}

陷阱2:多线程访问

private event EventHandler MyEvent;

// ❌ 不安全:在检查 null 和调用之间,事件可能被另一个线程清空
public void Trigger()
{
    if (MyEvent != null)
    {
        MyEvent(this, EventArgs.Empty);  // 可能为 null
    }
}

// ✅ 安全:复制本地副本
public void Trigger()
{
    var handler = MyEvent;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);  // 安全
    }
}

陷阱3:内存泄漏(忘记取消订阅)

// 示例:窗体关闭时忘记取消订阅
class MainForm
{
    private Timer _timer = new Timer();
    
    public MainForm()
    {
        _timer.Tick += Timer_Tick;  // 订阅
        _timer.Start();
    }
    
    private void Timer_Tick(object sender, EventArgs e)
    {
        // 即使 Form 关闭,Timer 仍然持有 Form 引用
    }
    
    // ✅ 正确:取消订阅
    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _timer.Tick -= Timer_Tick;
        base.OnFormClosing(e);
    }
}

10. 事件访问器(自定义 add/remove)

class CustomEventPublisher
{
    private EventHandler _myEvent;
    
    // 自定义事件访问器
    public event EventHandler MyEvent
    {
        add
        {
            Console.WriteLine("新的订阅者");
            _myEvent += value;
        }
        remove
        {
            Console.WriteLine("订阅者取消");
            _myEvent -= value;
        }
    }
    
    public void Trigger()
    {
        _myEvent?.Invoke(this, EventArgs.Empty);
    }
}

快速参考卡

// 声明事件
public event EventHandler MyEvent;
public event EventHandler<TEventArgs> MyEvent;
public event Action<int, string> MyEvent;

// 订阅事件
obj.MyEvent += HandlerMethod;
obj.MyEvent += (s, e) => Console.WriteLine("Lambda");

// 取消订阅
obj.MyEvent -= HandlerMethod;

// 触发事件
MyEvent?.Invoke(this, EventArgs.Empty);
MyEvent?.Invoke(this, new MyEventArgs());

// 标准模式
protected virtual void OnMyEvent(EventArgs e)
{
    MyEvent?.Invoke(this, e);
}

十四、Lambda

1. 一句话核心

概念说明
Lambda简洁的匿名函数写法
符号=>(读作 "goes to")
左边参数
右边表达式或语句块
本质编译器生成匿名方法或委托

2. 基本语法

// 语法结构:(参数) => 表达式/语句块

// 1. 无参数
Action func1 = () => Console.WriteLine("Hello");

// 2. 一个参数(可省略括号)
Func<int, int> square = x => x * x;

// 3. 多个参数(必须有括号)
Func<int, int, int> add = (a, b) => a + b;

// 4. 显式类型参数
Func<int, int, int> addExplicit = (int a, int b) => a + b;

// 5. 语句块(多条语句)
Func<int, int, int> multiply = (a, b) =>
{
    int result = a * b;
    Console.WriteLine($"计算:{a} * {b} = {result}");
    return result;
};

// 6. 无参数的 Lambda
Action greet = () => Console.WriteLine("你好");

3. Lambda 在 LINQ 中的使用(最常用场景)

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

// Where:筛选
var evens = numbers.Where(n => n % 2 == 0);        // 2,4,6,8,10

// Select:投影
var squares = numbers.Select(n => n * n);          // 1,4,9,16,25,...

// OrderBy:排序
var sorted = numbers.OrderBy(n => n % 2);          // 偶数在前

// Any:是否存在
bool hasEven = numbers.Any(n => n % 2 == 0);       // true

// All:是否全部满足
bool allPositive = numbers.All(n => n > 0);        // true

// First:第一个满足条件的
int firstEven = numbers.First(n => n % 2 == 0);    // 2

// 复杂的 Lambda
var result = numbers
    .Where(n => n > 5)                    // 大于5
    .Select(n => new { Original = n, Square = n * n })  // 投影成匿名类型
    .OrderByDescending(x => x.Square)      // 按平方降序
    .Take(3);                              // 取前3个

foreach (var item in result)
{
    Console.WriteLine($"{item.Original}{item.Square}");
}
// 输出:
// 10 → 100
// 9 → 81
// 8 → 64

4. Lambda 与委托的对比

// 传统方式:命名方法
bool IsEven(int n) => n % 2 == 0;
var evens1 = numbers.Where(IsEven);

// Lambda:匿名方法(更简洁)
var evens2 = numbers.Where(n => n % 2 == 0);

// 传统:匿名委托(C# 2.0)
var evens3 = numbers.Where(delegate(int n) { return n % 2 == 0; });

// Lambda 是最简洁的

5. Lambda 捕获外部变量

int multiplier = 10;
int offset = 5;

// Lambda 可以捕获外部变量
Func<int, int> transform = x => x * multiplier + offset;

Console.WriteLine(transform(3));  // 3*10+5=35

// 修改变量会影响 Lambda
multiplier = 20;
Console.WriteLine(transform(3));  // 3*20+5=65

// 注意:捕获变量的生命周期
Func<int> CreateCounter()
{
    int count = 0;
    return () => ++count;  // 捕获 count
}

var counter = CreateCounter();
Console.WriteLine(counter());  // 1
Console.WriteLine(counter());  // 2
Console.WriteLine(counter());  // 3
// count 被 Lambda 捕获,生命周期延长

6. 表达式 Lambda vs 语句 Lambda

// 表达式 Lambda:单表达式,无返回值(或直接返回)
Action a = () => Console.WriteLine("Hello");  // 无返回值
Func<int, int> square = x => x * x;           // 有返回值

// 语句 Lambda:大括号,多条语句
Func<int, int, int> max = (a, b) =>
{
    if (a > b) return a;
    return b;
};

Action<string> logger = msg =>
{
    Console.WriteLine($"[{DateTime.Now}] {msg}");
    System.Diagnostics.Debug.WriteLine(msg);
};

7. Lambda 作为委托和表达式树

// 委托(执行代码)
Func<int, int> addOne = x => x + 1;
int result = addOne(5);  // 编译成 IL,直接执行

// 表达式树(表示代码结构)
System.Linq.Expressions.Expression<Func<int, int>> addOneExpr = x => x + 1;
// 可以在运行时分析和修改表达式树

// 实际应用:EF Core 中使用表达式树转换为 SQL
// db.Users.Where(u => u.Age > 18)  → 转换成 SQL: WHERE Age > 18

8. 实际应用示例

示例1:事件处理

Button button = new Button();

// Lambda 作为事件处理器
button.Click += (sender, e) => Console.WriteLine("按钮被点击");
button.MouseEnter += (s, e) =>
{
    Console.WriteLine("鼠标进入");
    button.BackColor = Color.Yellow;
};

示例4:任务和异步

// 在线程中执行
Task.Run(() =>
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine(i);
        Thread.Sleep(100);
    }
});

// 定时器
var timer = new Timer(e =>
{
    Console.WriteLine($"Tick at {DateTime.Now}");
}, null, 0, 1000);

9. 常见陷阱

陷阱1:延迟执行

var numbers = new List<int> { 1, 2, 3 };
var query = numbers.Select(x => x * 2);

numbers.Add(4);  // 修改源数据

foreach (var item in query)
{
    Console.WriteLine(item);  // 输出:2,4,6,8(包含新增的4)
}
// 延迟执行:Lambda 在迭代时才执行

陷阱2:捕获循环变量

List<Action> actions = new List<Action>();

// ❌ 错误:捕获同一个变量
for (int i = 0; i < 5; i++)
{
    actions.Add(() => Console.WriteLine(i));  // 都捕获同一个 i
}
foreach (var action in actions)
{
    action();  // 输出 5,5,5,5,5
}

// ✅ 正确:复制局部变量
actions.Clear();
for (int i = 0; i < 5; i++)
{
    int temp = i;
    actions.Add(() => Console.WriteLine(temp));  // 捕获不同的 temp
}
foreach (var action in actions)
{
    action();  // 输出 0,1,2,3,4
}

陷阱3:过早优化

// 没问题,自然清晰
var result = data.Where(x => x.IsValid)
                 .Select(x => x.Value)
                 .OrderBy(x => x)
                 .ToList();

// 没必要把所有 Lambda 写成复杂链路

10. 性能考虑

// Lambda 和命名方法性能几乎一样(编译成相同 IL)
Func<int, int> lambda = x => x * 2;
int Method(int x) => x * 2;

// 但捕获变量会有额外开销
int factor = 2;
Func<int, int> capture = x => x * factor;  // 产生闭包,分配对象

Func<int, int> noCapture = x => x * 2;      // 无闭包,缓存为静态方法

十五、泛型

1. 一句话核心

概念说明
泛型定义类/方法时使用"占位符"类型,使用时再指定具体类型
类型参数<T> 表示占位符
好处类型安全 + 代码复用 + 避免装箱

2. 为什么需要泛型?

// ❌ 问题1:不用泛型,需要为每种类型写一遍
public class IntList
{
    private int[] _items = new int[10];
    public void Add(int item) { }
    public int Get(int index) => _items[index];
}

public class StringList
{
    private string[] _items = new string[10];
    public void Add(string item) { }
    public string Get(int index) => _items[index];
}
// 每个类型都要重复写,太痛苦了!

// ❌ 问题2:用 object 解决(但丢失类型安全,有装箱开销)
public class ObjectList
{
    private object[] _items = new object[10];
    public void Add(object item) { }
    public object Get(int index) => _items[index];
}

var list = new ObjectList();
list.Add(10);
list.Add("hello");
int num = (int)list.Get(0);  // 需要强制转换,可能出错
string str = (string)list.Get(1);

// ✅ 解决方案:泛型
public class GenericList<T>
{
    private T[] _items = new T[10];
    public void Add(T item) { }
    public T Get(int index) => _items[index];
}

var intList = new GenericList<int>();
intList.Add(10);
int num = intList.Get(0);  // 不需要转换,类型安全

var stringList = new GenericList<string>();
stringList.Add("hello");
string str = stringList.Get(0);

3. 基本语法

// 泛型类
public class Box<T>
{
    public T Value { get; set; }
    
    public Box(T value)
    {
        Value = value;
    }
    
    public T GetValue() => Value;
}

// 使用
Box<int> intBox = new Box<int>(123);
Box<string> strBox = new Box<string>("Hello");
Box<Person> personBox = new Box<Person>(new Person());

intBox.Value = 456;
string str = strBox.GetValue();

4. 泛型类

// 多个类型参数
public class Pair<TFirst, TSecond>
{
    public TFirst First { get; set; }
    public TSecond Second { get; set; }
    
    public Pair(TFirst first, TSecond second)
    {
        First = first;
        Second = second;
    }
    
    public void Print()
    {
        Console.WriteLine($"First: {First}, Second: {Second}");
    }
}

// 使用
var pair1 = new Pair<int, string>(1, "One");
var pair2 = new Pair<string, DateTime>("Now", DateTime.Now);
pair1.Print();  // First: 1, Second: One

// 类型推断(C# 泛型方法可以推断,但构造函数不行)
var pair3 = new Pair<int, string>(2, "Two");  // 必须显式指定

5. 泛型方法

public class Utilities
{
    // 泛型方法(可以不在泛型类中)
    public T Max<T>(T a, T b) where T : IComparable<T>
    {
        return a.CompareTo(b) > 0 ? a : b;
    }
    
    public void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
    
    public T[] CreateArray<T>(T value, int count)
    {
        T[] array = new T[count];
        for (int i = 0; i < count; i++)
            array[i] = value;
        return array;
    }
}

// 使用
var utils = new Utilities();
int maxInt = utils.Max(5, 10);        // 10
string maxStr = utils.Max("apple", "banana");  // "banana"

int x = 5, y = 10;
utils.Swap(ref x, ref y);  // x=10, y=5

int[] arr = utils.CreateArray(42, 5);  // [42,42,42,42,42]

// 类型推断:编译器自动推断 T
utils.Swap(ref x, ref y);      // 推断为 int
utils.Max(3.14, 2.71);         // 推断为 double

6. 泛型接口

// 泛型接口
public interface IRepository<T>
{
    T GetById(int id);
    void Add(T entity);
    void Delete(int id);
    IEnumerable<T> GetAll();
}

// 实现泛型接口
public class Repository<T> : IRepository<T> where T : class
{
    private Dictionary<int, T> _data = new();
    private int _nextId = 1;
    
    public T GetById(int id) => _data.GetValueOrDefault(id);
    
    public void Add(T entity)
    {
        _data[_nextId++] = entity;
    }
    
    public void Delete(int id)
    {
        _data.Remove(id);
    }
    
    public IEnumerable<T> GetAll() => _data.Values;
}

// 使用
public class Product { public string Name { get; set; } }

var productRepo = new Repository<Product>();
productRepo.Add(new Product { Name = "iPhone" });
var product = productRepo.GetById(1);

7. 泛型约束(where)

// 约束限制 T 可以是什么类型
public class ConstraintDemo
{
    // 1. 值类型约束
    public class ValueContainer<T> where T : struct
    {
        // T 只能是 int, double, DateTime 等值类型
    }
    ValueContainer<int> c1 = new();     // ✅
    // ValueContainer<string> c2 = new();  // ❌ string 不是值类型
    
    // 2. 引用类型约束
    public class RefContainer<T> where T : class
    {
        // T 只能是 class、interface、delegate 等引用类型
    }
    RefContainer<string> r1 = new();    // ✅
    RefContainer<object> r2 = new();    // ✅
    // RefContainer<int> r3 = new();        // ❌ int 不是引用类型
    
    // 3. 无参构造函数约束
    public class Creatable<T> where T : new()
    {
        public T Create()
        {
            return new T();  // 可以 new()
        }
    }
    Creatable<DateTime> c = new();  // DateTime 有默认构造函数
    
    // 4. 基类约束
    public class AnimalContainer<T> where T : Animal
    {
        // T 必须是 Animal 或其子类
    }
    AnimalContainer<Dog> dogContainer = new();  // ✅
    // AnimalContainer<string> strContainer = new();  // ❌
    
    // 5. 接口约束
    public class Comparable<T> where T : IComparable<T>
    {
        public int Compare(T a, T b) => a.CompareTo(b);
    }
    
    // 6. 多个约束组合
    public class StrictClass<T> where T : class, IComparable<T>, new()
    {
        // T 必须是:引用类型 + 可比较 + 有无参构造函数
    }
}

class Animal { }
class Dog : Animal { }

8. 泛型委托

// 声明泛型委托
public delegate T Transformer<T>(T input);

// 使用
Transformer<int> square = x => x * x;
Transformer<string> toUpper = s => s.ToUpper();

int result = square(5);        // 25
string str = toUpper("hello"); // "HELLO"

// 内置泛型委托(更常用)
Func<int, int, int> add = (a, b) => a + b;
Action<string> print = s => Console.WriteLine(s);
Predicate<int> isEven = n => n % 2 == 0;

9. 实际应用示例

示例1:通用缓存类

public class Cache<TKey, TValue>
{
    private Dictionary<TKey, TValue> _cache = new();
    private Dictionary<TKey, DateTime> _expiry = new();
    
    public void Set(TKey key, TValue value, TimeSpan ttl)
    {
        _cache[key] = value;
        _expiry[key] = DateTime.Now.Add(ttl);
    }
    
    public TValue Get(TKey key)
    {
        if (_cache.ContainsKey(key) && _expiry[key] > DateTime.Now)
            return _cache[key];
        
        throw new KeyNotFoundException("缓存已过期或不存在");
    }
    
    public bool TryGet(TKey key, out TValue value)
    {
        if (_cache.ContainsKey(key) && _expiry[key] > DateTime.Now)
        {
            value = _cache[key];
            return true;
        }
        value = default;
        return false;
    }
}

// 使用
var userCache = new Cache<int, User>();
userCache.Set(1, new User { Name = "张三" }, TimeSpan.FromMinutes(30));

if (userCache.TryGet(1, out var user))
{
    Console.WriteLine(user.Name);
}

示例2:通用结果包装类

public class Result<T>
{
    public bool IsSuccess { get; }
    public T Data { get; }
    public string Error { get; }
    
    private Result(bool isSuccess, T data, string error)
    {
        IsSuccess = isSuccess;
        Data = data;
        Error = error;
    }
    
    public static Result<T> Success(T data) => new(true, data, null);
    public static Result<T> Fail(string error) => new(false, default, error);
    
    public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<string, TResult> onFail)
    {
        return IsSuccess ? onSuccess(Data) : onFail(Error);
    }
}

// 使用
Result<int> ParseInt(string input)
{
    if (int.TryParse(input, out int result))
        return Result<int>.Success(result);
    return Result<int>.Fail($"无法解析 '{input}' 为整数");
}

var result = ParseInt("123");
result.Match(
    onSuccess: num => Console.WriteLine($"解析成功:{num}"),
    onFail: err => Console.WriteLine($"解析失败:{err}")
);

示例3:通用单例模式

public class Singleton<T> where T : class, new()
{
    private static T _instance;
    private static readonly object _lock = new();
    
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (_lock)
                {
                    _instance ??= new T();
                }
            }
            return _instance;
        }
    }
    
    private Singleton() { }
}

// 使用
class DatabaseConnection
{
    public void Connect() => Console.WriteLine("Connected");
}

var db = Singleton<DatabaseConnection>.Instance;
db.Connect();

10. 泛型中的默认值

public class DefaultExample<T>
{
    public T GetDefault()
    {
        return default(T);  // 返回 T 的默认值
        // 或者简写:return default;
    }
    
    public void CheckValue(T value)
    {
        if (EqualityComparer<T>.Default.Equals(value, default(T)))
        {
            Console.WriteLine("值是默认值");
        }
    }
}

// default(T) 结果:
// int → 0
// bool → false
// string → null
// 自定义类 → null
// 自定义结构体 → 所有字段为默认值

11. 协变和逆变(高级)

// out T:协变(只能作为返回值)
// in T:逆变(只能作为参数)

// 协变:可以将子类作为父类返回
IEnumerable<Dog> dogs = new List<Dog>();
IEnumerable<Animal> animals = dogs;  // ✅ IEnumerable<T> 是协变的

// 逆变:可以将父类作为参数传入
Action<Animal> actAnimal = a => Console.WriteLine(a);
Action<Dog> actDog = actAnimal;  // ✅ Action<T> 是逆变的

// 自定义协变接口
interface IProducer<out T>  // out = 协变
{
    T Produce();
}

class DogProducer : IProducer<Dog>
{
    public Dog Produce() => new Dog();
}

IProducer<Animal> animalProducer = new DogProducer();  // 协变

// 自定义逆变接口
interface IConsumer<in T>  // in = 逆变
{
    void Consume(T item);
}

class AnimalConsumer : IConsumer<Animal>
{
    public void Consume(Animal animal) { }
}

IConsumer<Dog> dogConsumer = new AnimalConsumer();  // 逆变

12. 常见陷阱

// 陷阱1:静态成员是类型独立的
class GenericClass<T>
{
    public static int Count = 0;
}

GenericClass<int>.Count = 10;
GenericClass<string>.Count = 20;
// Count 是独立的,不共享!int版本=10,string版本=20

// 陷阱2:不能使用类型参数做运算
T Add<T>(T a, T b) where T : struct
{
    // return a + b;  // ❌ 编译错误
    return default;  // 需要其他技术(表达式树等)
}

// 陷阱3:类型参数不能作为属性名
// public T T { get; set; }  // 可以但容易混淆,建议避免

快速参考卡

// 泛型类
class MyClass<T> { }

// 多个类型参数
class Pair<T1, T2> { }

// 泛型方法
T MyMethod<T>(T param) { }

// 泛型接口
interface IRepo<T> { }

// 泛型委托
delegate T MyDel<T>(T input);

// 约束
where T : struct        // 值类型
where T : class         // 引用类型
where T : new()         // 有无参构造
where T : BaseClass     // 指定基类
where T : IInterface    // 指定接口

// 内置泛型
List<T>                 // 可变列表
Dictionary<TKey, TValue> // 字典
Nullable<T>              // 可空类型(T?)
Func<T, TResult>        // 函数委托
Action<T>               // 动作委托

一句话总结

泛型是 C# 的类型参数化,用 <T> 占位具体类型,让代码既类型安全又可复用。List<T>Dictionary<K,V> 都是典型的泛型应用。配合 where 约束可以限制类型参数的行为。工作中:能用泛型就别用 object,能用 List<T> 就别用 ArrayList