C# 进阶知识点总结
适合初学者的C#进阶指南
目录
- 枚举 (enum)
- 结构体 (struct)
- 运算符重载
- 装箱与拆箱
- ArrayList 为什么不推荐
- 委托 (delegate)
- 事件 (event)
- Lambda 表达式
- in / out / ref 参数修饰符
- 泛型 (Generic)
1. 枚举 (enum)
枚举用于定义一组命名的常量,让代码更易读。
基本语法
// 定义枚举
public enum Weekday
{
Monday, // 默认从0开始
Tuesday, // 1
Wednesday, // 2
Thursday, // 3
Friday, // 4
Saturday, // 5
Sunday // 6
}
// 也可以手动指定值
public enum StatusCode
{
Success = 200,
NotFound = 404,
Error = 500
}
使用枚举
Weekday today = Weekday.Friday;
Console.WriteLine(today); // 输出: Friday
Console.WriteLine((int)today); // 输出: 4
StatusCode code = StatusCode.NotFound;
Console.WriteLine((int)code); // 输出: 404
枚举的底层
枚举本质上是整数,所以可以与 int 相互转换:
int dayNum = 3;
Weekday day = (Weekday)dayNum; // int → enum
Console.WriteLine(day); // Thursday
int num = (int)Weekday.Sunday; // enum → int
Console.WriteLine(num); // 6
switch 配合枚举
public string GetDayMessage(Weekday day)
{
switch (day)
{
case Weekday.Monday:
return "开始新的一周!";
case Weekday.Friday:
return "明天就是周末啦!";
case Weekday.Saturday:
case Weekday.Sunday:
return "休息日";
default:
return "工作日";
}
}
2. 结构体 (struct)
结构体是值类型,与类(引用类型)有重要区别。
值类型 vs 引用类型
| 特性 | 结构体 (struct) | 类 (class) |
|---|---|---|
| 类型 | 值类型 | 引用类型 |
| 存储位置 | 栈 (Stack) | 堆 (Heap) |
| 赋值行为 | 复制整个对象 | 只复制引用 |
| 默认值 | 自动初始化为零 | 默认 null |
| 性能 | 更轻量、更快 | 有GC开销 |
| 适用场景 | 小型数据结构 | 复杂对象、需要继承 |
图解
【值类型 (struct)】
┌─────────────────┐
│ 栈 │
│ ┌─────────────┐ │
│ │ a: Point │ │ ← 完整数据
│ │ x=10, y=20 │ │
│ └─────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ b: Point │ │ ← 复制了一份新数据
│ │ x=10, y=20 │ │
│ └─────────────┘ │
└─────────────────┘
【引用类型 (class)】
┌─────────────────┐ ┌─────────────────┐
│ 栈 │ │ 堆 │
│ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ a: Person │─┼───────│ Person对象 │ │
│ │ (引用地址) │ │ │ │ name="张三" │ │
│ └─────────────┘ │ │ └─────────────┘ │
│ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ b: Person │─┼─┐ │ │ Person对象 │ │
│ │ (引用地址) │ │ └─────│ name="李四" │ │
│ └─────────────┘ │ │ └─────────────┘ │
└─────────────────┘ └─────────────────┘
结构体示例
// 定义结构体
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public double DistanceTo(Point other)
{
int dx = X - other.X;
int dy = Y - other.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
}
// 使用
Point a = new Point(0, 0);
Point b = new Point(3, 4);
Console.WriteLine(a.DistanceTo(b)); // 5
赋值行为对比
// 结构体 - 赋值时复制整个对象
Point p1 = new Point(1, 2);
Point p2 = p1; // 复制了一份!
p2.X = 10;
Console.WriteLine(p1.X); // 1 ← p1没变
// 类 - 赋值时复制引用(共享同一个对象)
Person c1 = new Person { Name = "张三" };
Person c2 = c1; // 指向同一个对象
c2.Name = "李四";
Console.WriteLine(c1.Name); // 李四 ← c1也变了!
什么时候用结构体?
// ✅ 适合用结构体的场景:
public struct RGBColor { public byte R, G, B; } // 小型数据
public struct Vector3 { public float X, Y, Z; } // 几何数据
public struct DateTimeOffset { ... } // .NET内置值类型
// ❌ 不适合用结构体的场景:
public struct Employee { public string Name; ... } // 包含大对象
public struct Order { public List<Item> Items; } // 需要继承
3. 运算符重载
允许自定义类在使用运算符时的行为。
基本语法
public class Vector2
{
public int X { get; set; }
public int Y { get; set; }
public Vector2(int x, int y)
{
X = x;
Y = y;
}
// 重载 + 运算符
public static Vector2 operator +(Vector2 a, Vector2 b)
{
return new Vector2(a.X + b.X, a.Y + b.Y);
}
// 重载 == 运算符(必须同时重载 !=)
public static bool operator ==(Vector2 a, Vector2 b)
{
return a.X == b.X && a.Y == b.Y;
}
public static bool operator !=(Vector2 a, Vector2 b)
{
return !(a == b);
}
}
使用
Vector2 v1 = new Vector2(1, 2);
Vector2 v2 = new Vector2(3, 4);
Vector2 v3 = v1 + v2; // 自动调用我们的 + 重载
Console.WriteLine(v3.X); // 4
Console.WriteLine(v3.Y); // 6
可以重载的运算符
| 运算符 | 说明 |
|---|---|
+, -, *, /, % |
算术运算符 |
==, !=, <, >, <=, >= |
比较运算符 |
&&, \|\| |
不能重载! |
= |
不能重载 |
4. 装箱与拆箱
什么是装箱?
值类型 → 引用类型 的转换,需要在堆上分配内存。
int i = 123; // 值类型,在栈上
object o = i; // 装箱!把值类型包装成引用类型
// 内部发生的事:
// 1. 在堆上分配内存
// 2. 复制栈上的值到堆
// 3. 返回堆上的引用地址
什么是拆箱?
引用类型 → 值类型 的转换,需要类型检查。
object o = 123; // 装箱
int i = (int)o; // 拆箱!从堆上取回值类型
// 如果类型不匹配,会抛异常:
object o2 = "hello";
// int i2 = (int)o2; // ❌ InvalidCastException!
图解装箱拆箱
┌─────────────────────────────────────────────┐
│ 装箱 (Boxing) │
│ ┌───────┐ ┌───────────────────┐ │
│ │ int i │ ────► │ object o │ │
│ │ = 123 │ │ ┌───────────────┐ │ │
│ └───────┘ │ │ 123 (堆上) │ │ │
│ 栈 │ └───────────────┘ │ │
│ └───────────────────┘ │
│ 堆 │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 拆箱 (Unboxing) │
│ ┌───────────────────┐ ┌───────┐ │
│ │ object o │───►│ int i │ │
│ │ ┌───────────────┐ │ │ = 123 │ │
│ │ │ 123 (堆上) │ │ └───────┘ │
│ │ └───────────────┘ │ 栈 │
│ └───────────────────┘ │
│ 堆 ✓ 需要类型检查 │
└─────────────────────────────────────────────┘
性能问题
装箱拆箱有性能开销,应尽量避免:
// ❌ 性能差:大量装箱
ArrayList list = new ArrayList();
for (int i = 0; i < 100000; i++)
{
list.Add(i); // 每次都装箱!int → object
}
// ✅ 性能好:使用泛型
List<int> list2 = new List<int>();
for (int i = 0; i < 100000; i++)
{
list2.Add(i); // 没有装箱!
}
什么时候会发生装箱?
int num = 10;
// 这些都会装箱!
object o = num; // 直接赋值
Console.WriteLine(num); // Console.WriteLine 接收 object
ArrayList list = new ArrayList();
list.Add(num); // ArrayList.Add 接收 object
MethodThatTakesObject(num); // 方法参数是 object
// 解决办法:用泛型
List<int> list = new List<int>();
list.Add(num); // 不装箱!
5. ArrayList 为什么不推荐
ArrayList 的问题
// ArrayList 可以存储任意类型
ArrayList list = new ArrayList();
list.Add(10); // int → object (装箱)
list.Add("hello"); // string
list.Add(new Person()); // 对象
// 读取时返回 object,需要强制转换
int num = (int)list[0]; // 拆箱
string str = (string)list[1];
三大问题
| 问题 | 说明 |
|---|---|
| 装箱拆箱 | 存储值类型时会装箱,读取时拆箱,性能差 |
| 类型不安全 | 可以放入任何类型,编译时不检查 |
| 需要强制转换 | 读取时必须手动转换,容易出错 |
推荐:使用泛型 List
// ✅ 泛型List - 类型安全,没有装箱
List<int> numbers = new List<int>();
numbers.Add(10); // 直接添加,不会装箱
int num = numbers[0]; // 直接读取,不需要转换
List<string> words = new List<string>();
words.Add("hello");
string word = words[0]; // 类型安全,编译时就检查
// ❌ 编译错误!类型安全
numbers.Add("hello"); // 错误:不能把string放入List<int>
一句话总结
ArrayList = 装箱拆箱 + 类型不安全 + 强制转换 = 不推荐
List
= 无装箱拆箱 + 类型安全 + 无需转换 = 推荐
6. 委托 (delegate)
委托是一种类型安全的函数指针,可以引用方法。
基本概念
// 1. 声明委托类型
public delegate int Calculate(int a, int b);
// 2. 定义符合委托的方法
public int Add(int a, int b) { return a + b; }
public int Multiply(int a, int b) { return a * b; }
// 3. 创建委托实例
Calculate calc = Add; // 引用 Add 方法
Calculate calc2 = Multiply; // 引用 Multiply 方法
// 4. 调用
int result = calc(5, 3); // 调用 Add(5, 3),结果 8
int result2 = calc2(5, 3); // 调用 Multiply(5, 3),结果 15
委托的类型安全
// ❌ 编译错误!方法签名不匹配
Calculate calc = Add; // Add 签名是 (int, int) → int ✓
Calculate calc2 = "hello"; // 错误!不能赋字符串
// 方法参数或返回值不匹配也会报错
public int Subtract(int a, int b, int c) { return a - b - c; }
Calculate calc3 = Subtract; // ❌ 编译错误!参数个数不匹配
多播委托
一个委托可以引用多个方法:
public delegate void Action(string message);
public void SendEmail(string msg) { Console.WriteLine("邮件: {msg}"); }
public void SendSMS(string msg) { Console.WriteLine("短信: {msg}"); }
public void Log(string msg) { Console.WriteLine($"日志: {msg}"); }
// 创建多播委托
Action actions = SendEmail;
actions += SendSMS; // 添加
actions += Log; // 再添加
// 调用一次,所有方法都会执行
actions("你好!");
// 输出:
// 邮件: 你好!
// 短信: 你好!
// 日志: 你好!
// 移除方法
actions -= SendSMS;
actions("再见!");
// 输出:
// 邮件: 再见!
// 日志: 再见!
内置委托类型
C# 提供了常用的内置委托,不需要自己声明:
// Action - 无返回值的方法
Action<string> print = Console.WriteLine;
Action add = () => Console.WriteLine("添加成功");
// Func - 有返回值的方法
Func<int, int, int> calculate = (a, b) => a + b;
Func<int> getRandom = () => new Random().Next();
// Predicate - 返回 bool 的方法
Predicate<int> isEven = n => n % 2 == 0;
7. 事件 (event)
事件是基于委托的封装,限制外部只能订阅/取消订阅,不能直接调用。
为什么需要事件?
// ❌ 问题:用委托,外部可以直接调用(不安全)
public class Button
{
public Action OnClick; // 外部可以直接调用:button.OnClick()
}
Button btn = new Button();
btn.OnClick(); // 谁都可以随便调用!
用事件保护委托
// ✅ 事件 - 外部只能 += 和 -=,不能直接调用
public class Button
{
// 使用 event 关键字
public event Action OnClick;
public void Click()
{
Console.WriteLine("按钮被点击");
// 只有内部可以触发事件
OnClick?.Invoke(); // 安全调用
}
}
Button btn = new Button();
btn.OnClick += () => Console.WriteLine("处理点击1"); // 可以订阅
btn.OnClick += () => Console.WriteLine("处理点击2"); // 再次订阅
btn.Click(); // 输出:
// 按钮被点击
// 处理点击1
// 处理点击2
// ❌ 外部不能这样调用:
// btn.OnClick(); // 编译错误!
事件 vs 委托
| 特性 | 委托 | 事件 |
|---|---|---|
| 外部可以调用 | ✅ 可以 | ❌ 不可以 |
| 外部可以订阅 | ✅ 可以 | ✅ 可以 |
| 外部可以取消订阅 | ✅ 可以 | ✅ 可以 |
| 封装性 | 低 | 高 |
| 使用场景 | 回调、函数参数 | 通知机制 |
典型应用:观察者模式
public class Stock
{
// 事件
public event Action<decimal> PriceChanged;
private decimal _price;
public decimal Price
{
get => _price;
set
{
if (_price != value)
{
_price = value;
// 价格变化时通知所有订阅者
PriceChanged?.Invoke(_price);
}
}
}
}
// 使用
Stock apple = new Stock { Price = 100 };
apple.PriceChanged += (newPrice) =>
Console.WriteLine($"价格变为: {newPrice}");
apple.Price = 105; // 触发事件
apple.Price = 105; // 价格没变,不触发
// 输出: 价格变为: 105
8. Lambda 表达式
Lambda 是匿名函数的简写语法,常用于委托。
基本语法
// 完整写法
Func<int, int, int> add = (int a, int b) => { return a + b; };
// 简化:类型推断(最常用)
Func<int, int, int> add2 = (a, b) => { return a + b; };
// 简化:单表达式可省略 return 和 {}
Func<int, int, int> add3 = (a, b) => a + b;
// 单参数可省略括号
Predicate<int> isPositive = n => n > 0;
// 无参数
Action greet = () => Console.WriteLine("你好!");
Lambda 配合委托使用
// 传统方法
public bool IsEven(int n) { return n % 2 == 0; }
List<int> nums = new List<int> { 1, 2, 3, 4, 5 };
// 用 Lambda 过滤
List<int> evens = nums.FindAll(n => n % 2 == 0);
foreach (var n in evens) Console.WriteLine(n);
// 输出: 2, 4
// 用 Lambda 排序
nums.Sort((a, b) => b.CompareTo(a));
// 从大到小排序: 5, 4, 3, 2, 1
Lambda 在 LINQ 中的应用
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var result = numbers
.Where(n => n % 2 == 0) // 过滤偶数
.Select(n => n * n) // 平方
.OrderByDescending(n => n) // 降序排列
.Take(3); // 取前3个
foreach (var n in result)
Console.WriteLine(n);
// 输出: 100, 64, 36
9. in / out / ref 参数修饰符
ref - 传入引用,可读可写
public 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
out - 传出参数,必须返回
// 解析字符串返回多个值
public bool TryParse(string input, out int result)
{
try
{
result = int.Parse(input);
return true;
}
catch
{
result = 0;
return false;
}
}
if (int.TryParse("123", out int number))
{
Console.WriteLine($"解析成功: {number}");
}
else
{
Console.WriteLine("解析失败");
}
in - 只读传入(性能优化)
public double CalculateDistance(in Point a, in Point b)
{
// a 和 b 在方法内只能读取,不能修改
int dx = a.X - b.X; // ✓ 可以读取
// a.X = 100; // ❌ 编译错误!不能修改
return Math.Sqrt(dx * dx + (a.Y - b.Y) * (a.Y - b.Y));
}
// 优点:告诉编译器参数不会修改,可以安全地进行优化
三者对比
| 修饰符 | 方向 | 方法内能修改 | 调用时必须初始化 | 调用时必须加修饰符 |
|---|---|---|---|---|
| 无 | 值传递 | ❌ | 参数需要 | 不需要 |
ref |
双向 | ✅ | 参数需要 | 需要 (ref) |
out |
仅输出 | ✅ | 参数不需要 | 需要 (out) |
in |
仅输入 | ❌ | 参数需要 | 需要 (in) |
10. 泛型 (Generic)
泛型让你编写可复用的代码,同时保持类型安全。
为什么需要泛型?
// ❌ 问题:ArrayList 丧失类型信息
ArrayList list = new ArrayList();
list.Add(1);
list.Add("hello"); // 错误运行时才发现
string s = (string)list[0]; // 运行时异常!
// ✅ 解决:泛型 - 编译时类型检查
List<int> list2 = new List<int>();
list2.Add(1);
list2.Add("hello"); // ❌ 编译错误!
int num = list2[0]; // ✅ 不需要转换
基本语法
// 定义泛型类
public class Container<T>
{
private T _item;
public T Item
{
get => _item;
set => _item = value;
}
}
// 使用
Container<int> intContainer = new Container<int>();
intContainer.Item = 123;
Container<string> strContainer = new Container<string>();
strContainer.Item = "你好";
泛型方法
public T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}
Console.WriteLine(Max(3, 5)); // 5
Console.WriteLine(Max("apple", "banana")); // banana
泛型约束
// where T : class T 必须是引用类型
// where T : struct T 必须是值类型
// where T : new() T 必须有默认构造函数
// where T : Person T 必须是 Person 或其子类
// where T : IComparable T 必须实现 IComparable 接口
public class Repository<T> where T : class, new()
{
public List<T> GetAll()
{
return new List<T>(); // 因为有 new() 约束,可以创建实例
}
}
常用泛型类型
// 泛型集合
List<T> // 动态数组
Dictionary<TKey, TValue> // 键值对
HashSet<T> // 不重复集合
Queue<T> // 队列 (先进先出)
Stack<T> // 栈 (先进后出)
// 泛型类/接口
IComparable<T>
IEnumerable<T>
IComparer<T>





Comments NOTHING