一、类型
完整总结:float / double / int / long / decimal
1. 核心特性速查表
| 类型 | 存储大小 | 取值范围 | 精确性 | 能否存小数 | 典型场景 |
|---|
float | 32位 (4字节) | ±1.4e-45 ~ ±3.4e38 | ❌ 不精确 (约7位有效数字) | ✅ 能 | 图形学、GPU、嵌入式 |
double | 64位 (8字节) | ±4.9e-324 ~ ±1.8e308 | ❌ 不精确 (约15-16位有效数字) | ✅ 能 | 普通小数默认选择、科学计算 |
int | 32位 (4字节) | -2,147,483,648 ~ 2,147,483,647 (约±21亿) | ✅ 绝对精确 (整数范围内) | ❌ 不能 | 普通整数默认选择、ID、数量、索引 |
long | 64位 (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");
int itemCount = 100;
int userId = 1234567;
int maxInt = int.MaxValue;
Console.WriteLine("【int】32位整数");
Console.WriteLine($" 商品数量: {itemCount}");
Console.WriteLine($" 用户ID: {userId}");
Console.WriteLine($" int最大值: {maxInt:N0}");
Console.WriteLine($" 超过最大值会溢出: {(int)(maxInt + 1)}");
Console.WriteLine();
long orderId = 9876543210123456L;
long fileSize = 5_000_000_000L;
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
long maxLong = long.MaxValue;
Console.WriteLine("【long】64位整数");
Console.WriteLine($" 订单号: {orderId}");
Console.WriteLine($" 文件大小: {fileSize:N0} 字节");
Console.WriteLine($" 当前时间戳: {timestamp}");
Console.WriteLine($" long最大值: {maxLong:N0}");
Console.WriteLine();
float temperature = 36.5f;
float gpuWeight = 0.1234567f;
float floatPi = 3.14159265358979f;
Console.WriteLine("【float】32位浮点数(单精度)");
Console.WriteLine($" 体温: {temperature}");
Console.WriteLine($" GPU权重: {gpuWeight}");
Console.WriteLine($" 圆周率(单精度): {floatPi}");
Console.WriteLine($" 精度损失: 0.1f + 0.2f = {0.1f + 0.2f}");
Console.WriteLine($" 与0.3比较: {(0.1f + 0.2f == 0.3f)}");
Console.WriteLine();
double price = 19.99;
double science = 1.2345678901234567;
double doublePi = 3.14159265358979323846;
Console.WriteLine("【double】64位浮点数(双精度)");
Console.WriteLine($" 价格: {price: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)}");
Console.WriteLine();
decimal amount = 9.99m;
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}");
Console.WriteLine($" 计算结果: {result:C}");
Console.WriteLine($" 精确运算: 0.1m + 0.2m = {0.1m + 0.2m}");
Console.WriteLine($" 与0.3比较: {(0.1m + 0.2m == 0.3m)}");
Console.WriteLine();
Console.WriteLine("=== 实战:金额计算(千万不要用double/float)===");
double priceDouble = 9.99;
double quantityDouble = 3;
double totalDouble = priceDouble * quantityDouble;
Console.WriteLine($" double计算: {priceDouble} x {quantityDouble} = {totalDouble}");
double sumDouble = 0;
for (int i = 0; i < 100; i++) sumDouble += 0.01;
Console.WriteLine($" double累加100次0.01: {sumDouble}");
decimal priceDecimal = 9.99m;
decimal quantityDecimal = 3;
decimal totalDecimal = priceDecimal * quantityDecimal;
Console.WriteLine($" decimal计算: {priceDecimal} x {quantityDecimal} = {totalDecimal}");
decimal sumDecimal = 0;
for (int i = 0; i < 100; i++) sumDecimal += 0.01m;
Console.WriteLine($" decimal累加100次0.01: {sumDecimal}");
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;
public string? Name;
public bool IsActive;
public double Score;
public DateTime Birth;
}
当你 new Person() 时,所有字段都会被自动赋值为该类型的默认值。
required 的作用
required 是 C# 11 引入的特性,它的目的是强制调用方在对象初始化时显式赋值,而不是依赖默认值。
class Student
{
public int Id;
public required string Name;
}
Student s1 = new Student { Name = "张三", Id = 10 };
Student s2 = new Student { Id = 10 };
Student s3 = new Student { Name = null, Id = 10 };
1. public vs private 示例
class Person
{
public int Id;
private int age;
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;
p.SetAge(20);
Console.WriteLine(p.GetAge());
}
}
2. 访问修饰符完整对比
class Person
{
public string Name;
private int age;
protected string IdCard;
internal string Dept;
private protected int Salary;
protected internal string Email;
}
用 private 保护数据
class BankAccount_bad
{
public decimal Balance;
}
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 string name = null!;
public int Id
{
get { return id; }
set { id = value; }
}
public required string Name
{
get { return name; }
set { name = value; }
}
}
Student s = new Student { Name = "张三" };
s.Id = 90;
之前的代码优化
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; }
}
}
class Person_V2
{
public int Id { get; set; }
public string? Name { get; set; }
}
class Person_V3
{
public int Id { 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:赋值时要验证(最常用)
class Student_bad
{
public int Age;
}
var s = new Student_bad();
s.Age = -999;
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; }
}
}
var rect = new Rectangle();
rect.Width = 10;
rect.Height = 20;
Console.WriteLine(rect.Area);
场景3:只读或只写
class BankAccount
{
private decimal _balance;
public decimal Balance
{
get { return _balance; }
}
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);
场景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;
}
}
当你暂时不需要验证逻辑时,用自动属性就行。以后需要加验证时,再改成完整属性:
public int Age { get; set; }
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 = "张三";
var count = 10;
var list = new 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;
string name = null;
if (p != null)
{
name = p.Name;
}
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 简化(资源管理)
using var file = new StreamReader("test.txt");
string content = file.ReadToEnd();
StreamReader file = new StreamReader("test.txt");
try
{
string content = file.ReadToEnd();
}
finally
{
if (file != null) file.Dispose();
}
9. nameof 表达式
Console.WriteLine(nameof(Person.Name));
Console.WriteLine("Name");
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";
}
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 表达式
string GetGrade(int score)
{
switch (score)
{
case >= 90: return "A";
case >= 80: return "B";
case >= 70: return "C";
default: return "F";
}
}
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));
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, enum | class, interface, delegate, record, string, array, object |
| 默认值 | 0, false, 0.0等(不是null) | null |
| 可以 null? | ❌ 默认不能(除非用 int?) | ✅ 可以 |
| 内存位置 | 栈(局部变量) | 堆 |
| 赋值操作 | 复制整个数据 | 复制引用地址 |
== 含义 | 比较值 | 比较引用地址(是否同一对象) |
| 性能 | 栈上分配快,复制大数据慢 | 堆上分配慢,GC 有开销 |
3. 核心代码示例
值类型:赋值后独立
int a = 10;
int b = a;
b = 20;
Console.WriteLine(a);
Console.WriteLine(b);
Point p1 = new Point { X = 1, Y = 2 };
Point p2 = p1;
p2.X = 100;
Console.WriteLine(p1.X);
Console.WriteLine(p2.X);
引用类型:赋值后共享
class Person
{
public string Name { get; set; }
}
Person p1 = new Person { Name = "张三" };
Person p2 = p1;
p2.Name = "李四";
Console.WriteLine(p1.Name);
Console.WriteLine(p2.Name);
Console.WriteLine(p1 == p2);
4. 特殊情况:string
string 是引用类型,但行为像值类型(不可变):
string s1 = "hello";
string s2 = s1;
s2 = "world";
Console.WriteLine(s1);
Console.WriteLine(s2);
为什么这样做?为了安全性和字符串池优化。
5. 方法参数传递
void ChangeInt(int x)
{
x = 100;
}
int a = 10;
ChangeInt(a);
Console.WriteLine(a);
void ChangeName(Person p)
{
p.Name = "李四";
}
Person p1 = new Person { Name = "张三" };
ChangeName(p1);
Console.WriteLine(p1.Name);
void ChangeIntRef(ref int x)
{
x = 100;
}
int b = 10;
ChangeIntRef(ref b);
Console.WriteLine(b);
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);
PointClass pc1 = new PointClass { X = 1 };
PointClass pc2 = pc1;
pc2.X = 100;
Console.WriteLine(pc1.X);
陷阱2:集合中的值类型
List<Point> points = new List<Point>();
points.Add(new Point { X = 1, Y = 2 });
Point temp = points[0];
temp.X = 100;
points[0] = temp;
陷阱3:default 值不同
int i = default;
Person p = default;
Person p2 = new Person();
7. 如何选择:struct vs class
| 场景 | 用 struct(值类型) | 用 class(引用类型) |
|---|
| 数据大小 | 小(≤16字节推荐) | 大或小都可以 |
| 语义 | 代表一个值(如点、颜色、复数) | 代表一个对象(如人、订单、数据库连接) |
| 修改频率 | 创建后少修改 | 经常修改 |
| 装箱风险 | 经常装箱就不好(性能下降) | 不会装箱 |
| null 语义 | 不希望有 null | 可能为 null |
| 传递参数 | 想传递副本(不改原值) | 想传递引用(可改原值) |
struct Point { public int X, Y; }
class Order { public int Id; public decimal Total; }
8. 判断一个类型是值还是引用类型
Console.WriteLine(typeof(int).IsValueType);
Console.WriteLine(typeof(string).IsValueType);
Console.WriteLine(typeof(List<int>).IsValueType);
Console.WriteLine(typeof(DateTime).IsValueType);
Console.WriteLine(typeof(MyStruct).IsValueType);
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. 数组的声明和初始化
int[] numbers = new int[3];
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
int[] numbers2 = new int[] { 10, 20, 30 };
int[] numbers3 = { 10, 20, 30 };
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. 数组的默认值
int[] intArray = new int[3];
bool[] boolArray = new bool[3];
string[] strArray = new string[3];
int?[] nullableArray = new int?[3];
4. 访问和修改元素
int[] arr = { 10, 20, 30, 40, 50 };
int first = arr[0];
int last = arr[4];
arr[2] = 100;
for (int i = 0; i < arr.Length; i++)
{
Console.WriteLine($"索引{i}: {arr[i]}");
}
foreach (int num in arr)
{
Console.WriteLine(num);
}
foreach (int num in arr)
{
}
5. 数组的重要属性和方法
int[] arr = { 10, 20, 30, 40, 50 };
Console.WriteLine(arr.Length);
Array.Sort(arr);
Array.Reverse(arr);
int index = Array.IndexOf(arr, 30);
Array.Clear(arr, 0, arr.Length);
int[] copy = new int[3];
Array.Copy(arr, copy, 3);
int[] clone = (int[])arr.Clone();
List<int> list = arr.ToList();
string str = string.Join(", ", arr);
6. 引用类型特性(重要!)
因为数组是引用类型,赋值时复制的是地址:
int[] arr1 = { 1, 2, 3 };
int[] arr2 = arr1;
arr2[0] = 100;
Console.WriteLine(arr1[0]);
Console.WriteLine(arr2[0]);
int[] arr3 = (int[])arr1.Clone();
arr3[0] = 999;
Console.WriteLine(arr1[0]);
7. 常见陷阱
陷阱1:索引越界
int[] arr = { 1, 2, 3 };
陷阱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)
{
}
for (int i = 0; i < arr.Length; i++)
{
arr[i] = 100;
}
陷阱4:数组长度不可变
int[] arr = { 1, 2, 3 };
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]);
int[][] jagged = new int[2][];
jagged[0] = new int[] { 1, 2 };
jagged[1] = new int[] { 3, 4, 5, 6 };
Console.WriteLine(jagged[1][2]);
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<int> scores = new List<int>();
scores.Add(90);
scores.Add(85);
scores.Remove(90);
List<string> lines = new List<string>();
string line;
while ((line = reader.ReadLine()) != null)
lines.Add(line);
快速参考卡
int[] arr = new int[3];
int[] arr2 = { 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)
int[] copy = (int[])arr.Clone();
Array.Sort(arr);
六、异常
. 核心概念
| 概念 | 说明 |
|---|
| 异常 | 程序运行时发生的意外情况(如除以0、文件不存在) |
| 抛出异常 | 发生错误时,创建并"扔出"一个异常对象 |
| 捕获异常 | 用 try-catch 接住异常,进行处理 |
try | 包裹可能出错的代码 |
catch | 捕获并处理特定类型的异常 |
finally | 无论是否出错,都会执行的代码(如释放资源) |
throw | 手动抛出异常 |
2. 基本语法
try
{
int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"除以0错误:{ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"其他错误:{ex.Message}");
}
finally
{
Console.WriteLine("清理工作...");
}
3. 完整的异常处理示例
class Program
{
static void Main()
{
try
{
int a = 10;
int b = 0;
int result = a / b;
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"数学错误:{ex.Message}");
}
try
{
int[] arr = { 1, 2, 3 };
int x = arr[10];
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine($"索引错误:{ex.Message}");
}
try
{
string s = null;
int len = s.Length;
}
catch (NullReferenceException ex)
{
Console.WriteLine($"空引用错误:{ex.Message}");
}
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 | 文件不存在 |
IOException | I/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);
}
catch (ArgumentException ex)
{
Console.WriteLine($"参数错误:{ex.Message}");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"操作错误:{ex.Message}");
}
6. 自定义异常
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;
}
}
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;
}
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 (var reader = new StreamReader("file.txt"))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
10. 最佳实践
❌ 不好的做法
try { DoSomething(); }
catch (Exception) { }
try { DoSomething(); }
catch (Exception ex) { Console.WriteLine("出错了"); }
try { int.Parse(input); }
catch {
throw new Exception("出错了");
✅ 好的做法
try
{
var data = ParseUserInput(input);
}
catch (FormatException ex)
{
Console.WriteLine("输入格式错误,请重新输入");
return defaultValue;
}
throw new ArgumentException("用户名不能为空", nameof(username));
if (int.TryParse(input, out int result))
{
}
else
{
}
catch (Exception ex)
{
logger.LogError(ex, "处理订单失败,订单ID:{OrderId}", orderId);
throw;
}
void ProcessOrder(Order order)
{
if (order == null)
throw new ArgumentNullException(nameof(order));
if (order.Amount <= 0)
throw new ArgumentException("金额必须大于0", nameof(order));
}
11. 异常的性能影响
int ParseWithException(string input)
{
try
{
return int.Parse(input);
}
catch (FormatException)
{
return -1;
}
}
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} 在吃东西");
}
public virtual void MakeSound()
{
Console.WriteLine("动物发出声音");
}
}
class Dog : Animal
{
public string Breed { get; set; }
public void WagTail()
{
Console.WriteLine($"{Name} 摇尾巴");
}
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();
}
}
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 无参构造");
}
public Dog(string name, int age, string breed) : base(name, age)
{
Breed = breed;
Console.WriteLine($"Dog 构造:{breed}");
}
}
Dog dog1 = new Dog();
Dog dog2 = new Dog("旺财", 3, "金毛");
4. virtual / override vs new
class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal 叫");
}
public void Eat()
{
Console.WriteLine("Animal 吃");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Dog 汪汪叫");
}
public new void Eat()
{
Console.WriteLine("Dog 吃骨头");
}
}
Animal a = new Dog();
a.Speak();
a.Eat();
Dog d = new Dog();
d.Speak();
d.Eat();
5. 访问修饰符在继承中的影响
class Base
{
public int PublicField = 1;
private int PrivateField = 2;
protected int ProtectedField = 3;
internal int InternalField = 4;
protected internal int ProtectedInternal = 5;
private protected int PrivateProtected = 6;
}
class Derived : Base
{
void Test()
{
PublicField = 10;
ProtectedField = 30;
InternalField = 40;
ProtectedInternal = 50;
PrivateProtected = 60;
}
}
6. 抽象类 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;
}
public override double GetArea()
{
return Math.PI * Radius * Radius;
}
public override double GetPerimeter()
{
return 2 * Math.PI * Radius;
}
}
Circle c = new Circle(5);
c.Display();
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();
tesla.Honk();
tesla.Charge();
8. sealed 封闭类(禁止继承)
sealed class FinalClass
{
public void Method() { }
}
class Parent
{
public virtual void Method1() { }
public virtual void Method2() { }
}
class Child : Parent
{
public override void Method1() { }
public sealed override void Method2() { }
}
class GrandChild : Child
{
public override void Method1() { }
}
9. 继承的传递性
class A
{
public void MethodA() { }
}
class B : A
{
public void MethodB() { }
}
class C : B
{
public void MethodC() { }
}
C obj = new C();
obj.MethodA();
obj.MethodB();
obj.MethodC();
10. object 是所有类的基类
class MyClass { }
struct MyStruct { }
class MyClass : 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 Animal | Car has a Engine |
| 子类可以替换父类 | 类包含另一个类的实例 |
| 需要多态行为 | 只需功能复用 |
| 层次结构稳定 | 关系可能变化 |
class Employee : Person { }
class Car
{
private Engine _engine = new Engine();
}
class Truck : Engine { }
12. 多重继承的替代:接口
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 的区别
| virtual | abstract |
|---|
| 有无默认实现 | ✅ 有 | ❌ 没有 |
| 子类是否必须重写 | ❌ 可选(可以重写,也可以不重写) | ✅ 必须重写(除非子类也是抽象类) |
| 所在类 | 可以放在普通类或抽象类 | 只能放在抽象类 |
| 方法体 | 有 { } | 没有 { },以 ; 结尾 |
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. 基本语法
interface IAnimal
{
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 抽象类
| 特性 | 接口 | 抽象类 |
|---|
| 关键字 | interface | abstract class |
| 多重实现 | ✅ 可以实现多个 | ❌ 只能继承一个 |
| 字段 | ❌ 不能有实例字段 | ✅ 可以有 |
| 实现代码 | ❌ 不能有实现(C# 8 之前) | ✅ 可以有 |
| 访问修饰符 | 默认 public,不能加 | ✅ 可以 |
| 构造函数 | ❌ 不能 | ✅ 可以 |
| 语义 | "能做什么" (can-do) | "是什么" (is-a) |
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()
{
Console.WriteLine("狗吃东西");
}
public void GiveBirth()
{
Console.WriteLine("狗生小狗");
}
}
Dog dog = new Dog();
dog.Eat();
dog.GiveBirth();
IMammal mammal = dog;
mammal.Eat();
mammal.GiveBirth();
IAnimal animal = dog;
animal.Eat();
6. 接口中的属性
interface IPerson
{
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("默认实现");
}
void IFile.Save()
{
Console.WriteLine("保存到文件");
}
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+)
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;
}
}
Circle circle = new Circle { Radius = 5 };
circle.Display();
10. 常用内置接口
class ResourceHolder : IDisposable
{
public void Dispose()
{
Console.WriteLine("释放资源");
}
}
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();
}
}
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);
}
}
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,可指定为 byte、short、long 等整数类型 |
| 值 | 默认从 0 开始递增,可以手动指定 |
| 用途 | 表示状态、选项、类别等有限集合 |
2. 基本语法
enum Weekday
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
class Program
{
static void Main()
{
Weekday today = Weekday.Monday;
Console.WriteLine(today);
Console.WriteLine((int)today);
if (today == Weekday.Monday)
{
Console.WriteLine("周一,开始工作");
}
switch (today)
{
case Weekday.Saturday:
case Weekday.Sunday:
Console.WriteLine("周末");
break;
default:
Console.WriteLine("工作日");
break;
}
}
}
3. 指定值和底层类型
enum StatusCode
{
Success = 200,
NotFound = 404,
ServerError = 500
}
enum Priority : byte
{
Low = 1,
Medium = 5,
High = 10
}
enum ErrorCode
{
None = 0,
Unknown = -1,
NetworkError = 100,
Timeout = 100
}
Console.WriteLine((int)StatusCode.Success);
Console.WriteLine((byte)Priority.High);
4. 枚举作为标志位 [Flags]
用于表示可以组合的选项:
[Flags]
enum FileAccess
{
None = 0,
Read = 1,
Write = 2,
Execute = 4,
Delete = 8
}
class Program
{
static void Main()
{
FileAccess access = FileAccess.Read | FileAccess.Write;
Console.WriteLine(access);
bool canRead = (access & FileAccess.Read) == FileAccess.Read;
bool canDelete = access.HasFlag(FileAccess.Delete);
access |= FileAccess.Execute;
Console.WriteLine(access);
access &= ~FileAccess.Write;
Console.WriteLine(access);
Console.WriteLine($"二进制: {Convert.ToString((int)access, 2)}");
}
}
十、ref和out
1. 一句话核心区别
| ref | out |
|---|
| 传入前必须初始化 | ✅ 是 | ❌ 否 |
| 方法内必须赋值 | ❌ 否 | ✅ 是 |
| 用途 | 修改现有变量 | 返回多个值 |
2. 代码直接对比
int x = 10;
AddOne(ref x);
Console.WriteLine(x);
void AddOne(ref int num)
{
num++;
}
int y;
SetToTen(out y);
Console.WriteLine(y);
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);
out:方法内必须赋值
int result;
TryParse("123", out result);
bool TryParse(string input, out int output)
{
if (int.TryParse(input, out output))
{
return true;
}
output = 0;
return false;
}
void BadMethod(out int value)
{
}
4. 典型使用场景
场景1:out 用于返回多个值(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}");
场景3:ref 用于性能优化(大结构体)
struct LargeStruct
{
public long Data1, Data2, Data3, Data4, Data5;
}
void Process(ref LargeStruct data)
{
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;
}
void ByRef(ref int x)
{
x = 100;
}
void ByOut(out int x)
{
x = 100;
}
int a = 10;
int b = 10;
int c;
ByValue(a);
Console.WriteLine(a);
ByRef(ref b);
Console.WriteLine(b);
ByOut(out c);
Console.WriteLine(c);
6. 引用类型参数传递
很多人困惑:引用类型(如 class)本身是引用,还需要 ref 吗?
class Person
{
public string Name { get; set; }
}
void ChangeName(Person p)
{
p.Name = "李四";
p = new Person { Name = "王五" };
}
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);
int.TryParse("123", out int newResult);
int.TryParse("123", out var result);
int.TryParse("123", out _);
十一、struct和class
实际规则:什么时候用什么?
struct Point { public int X, Y; }
struct Vector3 { public float X, Y, Z; }
struct Complex { public double Real, Imag; }
struct BigData
{
public int A1, A2, A3, A4, A5, A6, A7, A8, A9, A10;
}
struct MutablePoint { public int X, Y; }
List<MutablePoint> points = ...;
points[0].X = 10;
class Player { public string Name; public int Score; public float X, Y, Z; }
class Animal { }
class Dog : Animal { }
struct Config { public int MaxRetry; public float Timeout; }
"官方"指导原则(Microsoft)
微软推荐的 struct 使用条件(全部满足才用 struct):
- 逻辑上是一个值(像 int、double 一样)
- 实例很小(≤ 16 字节)
- 不可变(创建后不修改)
- 不需要频繁装箱
struct Point { public int X { get; } public int Y { get; } }
class Customer { public string Name; public int Age; }
性能实测数据
using System.Diagnostics;
struct SmallStruct { public int X, Y; }
class SmallClass { public int X, Y; }
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; }
你的理解修正版
| 你说的 | 实际情况 |
|---|
| "结构体定义对象里有什么字段" | ✅ 正确 |
| "class 也可以" | ✅ 正确 |
| "class 性能更差" | ⚠️ 不一定:小对象 struct 快,大对象 class 快 |
一句话总结
小对象(≤16字节)且逻辑上是"值"的用 struct(性能好);大对象或逻辑上是"实体"的用 class(性能好)。不是 struct 永远比 class 快,选错了反而更慢。
就像是:自行车在城市短途比汽车快(结构体在小数据场景),但长途运输肯定用汽车(类在大数据场景)。没有绝对的好坏,只有是否合适。
十二、委托
1. 一句话核心
| 概念 | 说明 |
|---|
| 委托 | 指向方法的引用类型 |
| 作用 | 把方法当作参数传递、延迟执行 |
| 声明 | delegate 返回值类型 委托名(参数); |
| 本质 | 继承自 System.Delegate 的类 |
2. 基本语法
delegate void PrintDelegate(string message);
delegate int MathDelegate(int a, int b);
PrintDelegate print = Console.WriteLine;
MathDelegate add = Add;
print("Hello");
int result = add(3, 5);
int Add(int a, int b) => a + b;
3. 委托的四种使用方式
class Program
{
static void Main()
{
PrintDelegate d1 = PrintToConsole;
d1("Hello 1");
var logger = new Logger();
PrintDelegate d2 = logger.PrintToFile;
d2("Hello 2");
PrintDelegate d3 = delegate(string msg)
{
Console.WriteLine($"匿名方法:{msg}");
};
d3("Hello 3");
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);
int diff = calc.Calculate(10, 5, Subtract);
int product = calc.Calculate(10, 5, Multiply);
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. 内置委托:Action、Func、Predicate
C# 提供常用委托,无需自己声明:
Action print = () => Console.WriteLine("Hello");
Action<string> printMsg = msg => Console.WriteLine(msg);
Action<int, int> printSum = (a, b) => Console.WriteLine(a + b);
Func<int> getRandom = () => new Random().Next();
Func<int, int, int> add = (a, b) => a + b;
Func<string, int> getLength = s => s.Length;
Predicate<int> isPositive = x => x > 0;
Predicate<string> isEmpty = s => string.IsNullOrEmpty(s);
int result = add(3, 5);
int len = getLength("Hello");
bool positive = isPositive(10);
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 };
var evens = numbers.Where(n => n % 2 == 0);
var strings = numbers.Select(n => $"数字:{n}");
var sorted = numbers.OrderBy(n => -n);
场景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 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<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);
9. 委托与接口的对比
class Processor
{
public void Process(int data, Func<int, int> transform)
{
int result = transform(data);
Console.WriteLine(result);
}
}
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();
foreach (Func<int> d in getNumber.GetInvocationList())
{
Console.WriteLine(d());
}
陷阱2:捕获变量
List<Action> actions = new List<Action>();
for (int i = 0; i < 5; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
{
action();
}
for (int i = 0; i < 5; i++)
{
int temp = i;
actions.Add(() => Console.WriteLine(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;
Action<int> a1;
Func<int> f;
Func<int,string> f1;
Predicate<int> p;
十三、事件
. 一句话核心
| 概念 | 说明 |
|---|
| 事件 | 封装后的委托,只能从声明它的类内部调用 |
| 发布者 | 触发事件的类 |
| 订阅者 | 响应事件的类 |
| 关键字 | event |
| 本质 | 委托的一个实例,加了访问限制 |
2. 基本语法
public delegate void MyEventHandler(object sender, EventArgs e);
public event MyEventHandler MyEvent;
protected virtual void OnMyEvent()
{
MyEvent?.Invoke(this, EventArgs.Empty);
}
3. 完整示例
class Button
{
public event EventHandler Click;
public void OnClick()
{
Console.WriteLine("按钮被点击了");
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. 自定义事件参数
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);
5. 事件 vs 委托
class Test
{
public Action MyDelegate;
public event Action MyEvent;
public void TestMethod()
{
MyDelegate?.Invoke();
MyEvent?.Invoke();
}
}
Test test = new Test();
test.MyDelegate = () => Console.WriteLine("Hello");
test.MyDelegate();
test.MyDelegate = null;
test.MyEvent += () => Console.WriteLine("Hello");
test.MyEvent -= () => Console.WriteLine("Hello");
6. 标准事件模式(.NET 规范)
public class FileProcessedEventArgs : EventArgs
{
public string FileName { get; set; }
public long FileSize { get; set; }
}
public class FileProcessor
{
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);
}
}
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 控件事件
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 = "张三";
person.Age = 25;
8. 弱事件模式(避免内存泄漏)
class Publisher
{
public event EventHandler DataArrived;
}
class Subscriber
{
public Subscriber(Publisher publisher)
{
publisher.DataArrived += OnDataArrived;
}
private void OnDataArrived(object sender, EventArgs e) { }
}
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
public void Trigger()
{
MyEvent(this, EventArgs.Empty);
}
public void Trigger()
{
MyEvent?.Invoke(this, EventArgs.Empty);
}
陷阱2:多线程访问
private event EventHandler MyEvent;
public void Trigger()
{
if (MyEvent != null)
{
MyEvent(this, EventArgs.Empty);
}
}
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)
{
}
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. 基本语法
Action func1 = () => Console.WriteLine("Hello");
Func<int, int> square = x => x * x;
Func<int, int, int> add = (a, b) => a + b;
Func<int, int, int> addExplicit = (int a, int b) => a + b;
Func<int, int, int> multiply = (a, b) =>
{
int result = a * b;
Console.WriteLine($"计算:{a} * {b} = {result}");
return result;
};
Action greet = () => Console.WriteLine("你好");
3. Lambda 在 LINQ 中的使用(最常用场景)
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evens = numbers.Where(n => n % 2 == 0);
var squares = numbers.Select(n => n * n);
var sorted = numbers.OrderBy(n => n % 2);
bool hasEven = numbers.Any(n => n % 2 == 0);
bool allPositive = numbers.All(n => n > 0);
int firstEven = numbers.First(n => n % 2 == 0);
var result = numbers
.Where(n => n > 5)
.Select(n => new { Original = n, Square = n * n })
.OrderByDescending(x => x.Square)
.Take(3);
foreach (var item in result)
{
Console.WriteLine($"{item.Original} → {item.Square}");
}
4. Lambda 与委托的对比
bool IsEven(int n) => n % 2 == 0;
var evens1 = numbers.Where(IsEven);
var evens2 = numbers.Where(n => n % 2 == 0);
var evens3 = numbers.Where(delegate(int n) { return n % 2 == 0; });
5. Lambda 捕获外部变量
int multiplier = 10;
int offset = 5;
Func<int, int> transform = x => x * multiplier + offset;
Console.WriteLine(transform(3));
multiplier = 20;
Console.WriteLine(transform(3));
Func<int> CreateCounter()
{
int count = 0;
return () => ++count;
}
var counter = CreateCounter();
Console.WriteLine(counter());
Console.WriteLine(counter());
Console.WriteLine(counter());
6. 表达式 Lambda vs 语句 Lambda
Action a = () => Console.WriteLine("Hello");
Func<int, int> square = x => x * x;
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);
System.Linq.Expressions.Expression<Func<int, int>> addOneExpr = x => x + 1;
8. 实际应用示例
示例1:事件处理
Button button = new Button();
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:捕获循环变量
List<Action> actions = new List<Action>();
for (int i = 0; i < 5; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
{
action();
}
actions.Clear();
for (int i = 0; i < 5; i++)
{
int temp = i;
actions.Add(() => Console.WriteLine(temp));
}
foreach (var action in actions)
{
action();
}
陷阱3:过早优化
var result = data.Where(x => x.IsValid)
.Select(x => x.Value)
.OrderBy(x => x)
.ToList();
10. 性能考虑
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. 为什么需要泛型?
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];
}
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();
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);
string maxStr = utils.Max("apple", "banana");
int x = 5, y = 10;
utils.Swap(ref x, ref y);
int[] arr = utils.CreateArray(42, 5);
utils.Swap(ref x, ref y);
utils.Max(3.14, 2.71);
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)
public class ConstraintDemo
{
public class ValueContainer<T> where T : struct
{
}
ValueContainer<int> c1 = new();
public class RefContainer<T> where T : class
{
}
RefContainer<string> r1 = new();
RefContainer<object> r2 = new();
public class Creatable<T> where T : new()
{
public T Create()
{
return new T();
}
}
Creatable<DateTime> c = new();
public class AnimalContainer<T> where T : Animal
{
}
AnimalContainer<Dog> dogContainer = new();
public class Comparable<T> where T : IComparable<T>
{
public int Compare(T a, T b) => a.CompareTo(b);
}
public class StrictClass<T> where T : class, IComparable<T>, new()
{
}
}
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);
string str = toUpper("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);
}
public void CheckValue(T value)
{
if (EqualityComparer<T>.Default.Equals(value, default(T)))
{
Console.WriteLine("值是默认值");
}
}
}
11. 协变和逆变(高级)
IEnumerable<Dog> dogs = new List<Dog>();
IEnumerable<Animal> animals = dogs;
Action<Animal> actAnimal = a => Console.WriteLine(a);
Action<Dog> actDog = actAnimal;
interface IProducer<out T>
{
T Produce();
}
class DogProducer : IProducer<Dog>
{
public Dog Produce() => new Dog();
}
IProducer<Animal> animalProducer = new DogProducer();
interface IConsumer<in T>
{
void Consume(T item);
}
class AnimalConsumer : IConsumer<Animal>
{
public void Consume(Animal animal) { }
}
IConsumer<Dog> dogConsumer = new AnimalConsumer();
12. 常见陷阱
class GenericClass<T>
{
public static int Count = 0;
}
GenericClass<int>.Count = 10;
GenericClass<string>.Count = 20;
T Add<T>(T a, T b) where T : struct
{
return default;
}
快速参考卡
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>
Func<T, TResult>
Action<T>
一句话总结
泛型是 C# 的类型参数化,用 <T> 占位具体类型,让代码既类型安全又可复用。List<T>、Dictionary<K,V> 都是典型的泛型应用。配合 where 约束可以限制类型参数的行为。工作中:能用泛型就别用 object,能用 List<T> 就别用 ArrayList。