2026-06-21 09:51:55 +08:00
|
|
|
|
# 构造函数
|
2026-06-18 20:01:30 +08:00
|
|
|
|
概念:
|
2026-06-21 09:51:55 +08:00
|
|
|
|
- 构造函数用于在对象创建时做初始化
|
|
|
|
|
|
- 构造函数名必须和类名相同
|
|
|
|
|
|
- 构造函数没有返回值类型
|
|
|
|
|
|
- ==可以重载==
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
|
|
|
|
|
通常用法:
|
2026-06-21 09:51:55 +08:00
|
|
|
|
- 给字段或属性设置初始值
|
|
|
|
|
|
- 保证对象一创建出来就处于可用状态
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
|
|
|
|
|
语法:
|
2026-06-21 09:51:55 +08:00
|
|
|
|
```csharp
|
|
|
|
|
|
访问修饰符 类名(参数列表)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
|
|
|
|
|
案例:
|
2026-06-21 09:51:55 +08:00
|
|
|
|
```csharp
|
|
|
|
|
|
class Person
|
2026-06-18 20:01:30 +08:00
|
|
|
|
{
|
2026-06-21 09:51:55 +08:00
|
|
|
|
public string Name;
|
|
|
|
|
|
public int Age;
|
|
|
|
|
|
|
2026-06-18 20:01:30 +08:00
|
|
|
|
public Person()
|
|
|
|
|
|
{
|
2026-06-21 09:51:55 +08:00
|
|
|
|
Name = "默认值";
|
|
|
|
|
|
Age = 0;
|
2026-06-18 20:01:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-21 09:51:55 +08:00
|
|
|
|
public Person(string name, int age)
|
2026-06-18 20:01:30 +08:00
|
|
|
|
{
|
2026-06-21 09:51:55 +08:00
|
|
|
|
Name = name;
|
|
|
|
|
|
Age = age;
|
2026-06-18 20:01:30 +08:00
|
|
|
|
}
|
2026-06-21 09:51:55 +08:00
|
|
|
|
}
|
2026-06-18 20:01:30 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-21 09:51:55 +08:00
|
|
|
|
`this` 调用其他构造函数:
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
class Person
|
|
|
|
|
|
{
|
|
|
|
|
|
public string Name;
|
|
|
|
|
|
public int Age;
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
2026-06-21 09:51:55 +08:00
|
|
|
|
public Person() : this("默认值", 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
2026-06-21 09:51:55 +08:00
|
|
|
|
public Person(string name, int age)
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = name;
|
|
|
|
|
|
Age = age;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
2026-06-21 09:51:55 +08:00
|
|
|
|
注意:
|
|
|
|
|
|
1. ==如果类里完全没写任何实例构造函数,编译器才会自动补一个无参构造函数==
|
|
|
|
|
|
2. ==只要你自己写了任意实例构造函数,默认无参构造函数就不会再自动生成==
|
|
|
|
|
|
3. 构造函数通常写 `public`,但也可以根据设计写成 `private` / `protected`
|
|
|
|
|
|
4. 子类构造函数可以通过 `base(...)` 调用父类构造函数
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
|
|
|
|
|
----
|
|
|
|
|
|
# 析构函数
|
|
|
|
|
|
概念:
|
2026-06-21 09:51:55 +08:00
|
|
|
|
- C# 里的析构函数更准确地说是==终结器(finalizer)==
|
|
|
|
|
|
- 当对象即将被 GC 回收时,运行时可能会调用它
|
|
|
|
|
|
- 它主要用于“兜底清理非托管资源”
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
|
|
|
|
|
语法:
|
2026-06-21 09:51:55 +08:00
|
|
|
|
```csharp
|
|
|
|
|
|
class Person
|
2026-06-18 20:01:30 +08:00
|
|
|
|
{
|
2026-06-21 09:51:55 +08:00
|
|
|
|
~Person()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 清理代码
|
|
|
|
|
|
}
|
2026-06-18 20:01:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
注意:
|
2026-06-21 09:51:55 +08:00
|
|
|
|
1. 析构函数只能写在==类==里,结构体不能写
|
|
|
|
|
|
2. 不能手动直接调用析构函数
|
|
|
|
|
|
3. 析构函数执行时机==不确定==
|
|
|
|
|
|
4. 大多数业务代码里==不推荐为了练习而滥写析构函数==
|
|
|
|
|
|
5. 现代 .NET 中,更常见的做法是优先使用 `IDisposable`、`using` 和 `SafeHandle`
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
|
|
|
|
|
----
|
|
|
|
|
|
# 垃圾回收
|
2026-06-21 09:51:55 +08:00
|
|
|
|
垃圾回收(GC):
|
|
|
|
|
|
- GC 是 .NET 的自动内存管理机制
|
|
|
|
|
|
- 它负责识别哪些托管对象还“活着”,哪些已经不可达,可以回收
|
|
|
|
|
|
|
|
|
|
|
|
基础理解:
|
|
|
|
|
|
1. 只要对象还能从“根”访问到,就一般不会被回收
|
|
|
|
|
|
2. 访问不到的对象,才有机会被 GC 回收
|
|
|
|
|
|
3. GC 主要管理的是==托管堆上的对象==
|
|
|
|
|
|
|
|
|
|
|
|
分代概念:
|
|
|
|
|
|
- .NET GC 常分为 0、1、2 代
|
|
|
|
|
|
- 新对象一般先进入 0 代
|
|
|
|
|
|
- 活得越久,越可能被提升到更高代
|
|
|
|
|
|
- 大对象通常会进入 LOH(大对象堆),阈值常见记法是 >= `85,000` 字节
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
|
|
|
|
|
注意:
|
2026-06-21 09:51:55 +08:00
|
|
|
|
1. ==`GC.Collect()` 一般不建议手动乱调==
|
|
|
|
|
|
2. 手动触发 GC 可能带来性能抖动
|
|
|
|
|
|
3. “值类型一定在栈上、引用类型一定在堆上”不是绝对规则
|
|
|
|
|
|
4. GC 解决的是==托管内存==问题,不等于所有资源都会自动及时释放
|
|
|
|
|
|
|
|
|
|
|
|
----
|
|
|
|
|
|
# GC 和 IDisposable
|
|
|
|
|
|
首先区分资源类型:
|
|
|
|
|
|
|
|
|
|
|
|
- **托管资源**
|
|
|
|
|
|
比如普通对象、字符串、集合。主要由 GC 管理。
|
|
|
|
|
|
- **非托管资源**
|
|
|
|
|
|
比如文件句柄、数据库连接、Socket、窗口句柄。需要更及时地手动释放。
|
|
|
|
|
|
|
|
|
|
|
|
为什么要用 `IDisposable`:
|
|
|
|
|
|
- 因为 GC 只负责“对象内存”回收
|
|
|
|
|
|
- 但像文件、连接、句柄这类资源,经常需要你主动关闭
|
|
|
|
|
|
|
|
|
|
|
|
最常见写法:
|
2026-06-18 20:01:30 +08:00
|
|
|
|
```csharp
|
2026-06-21 09:51:55 +08:00
|
|
|
|
using var stream = new FileStream("demo.txt", FileMode.OpenOrCreate);
|
|
|
|
|
|
// 使用 stream
|
|
|
|
|
|
```
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
2026-06-21 09:51:55 +08:00
|
|
|
|
说明:
|
|
|
|
|
|
- `using` 结束时会自动调用 `Dispose()`
|
|
|
|
|
|
- 这比“等 GC 什么时候想起来再收”更可靠
|
|
|
|
|
|
|
|
|
|
|
|
标准释放模式示意:
|
|
|
|
|
|
```csharp
|
2026-06-18 20:01:30 +08:00
|
|
|
|
public class MyResource : IDisposable
|
|
|
|
|
|
{
|
2026-06-21 09:51:55 +08:00
|
|
|
|
private bool _disposed;
|
|
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
Dispose(true);
|
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_disposed) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (disposing)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 释放托管资源
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 释放非托管资源
|
|
|
|
|
|
_disposed = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
~MyResource()
|
|
|
|
|
|
{
|
|
|
|
|
|
Dispose(false);
|
|
|
|
|
|
}
|
2026-06-18 20:01:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-21 09:51:55 +08:00
|
|
|
|
补充理解:
|
|
|
|
|
|
1. `Dispose()` 是“我现在就主动释放”
|
|
|
|
|
|
2. 析构函数是“万一你忘了,我最后帮你兜底一次”
|
|
|
|
|
|
3. `GC.SuppressFinalize(this)` 的意思是:
|
|
|
|
|
|
这个对象已经手动释放好了,不用再走终结器那一套
|
|
|
|
|
|
|
|
|
|
|
|
复习重点:
|
|
|
|
|
|
- GC 管托管内存
|
|
|
|
|
|
- `Dispose` 管需要及时释放的资源
|
|
|
|
|
|
- 最常用姿势不是手写 `GC.Collect()`,而是优先写好 `using` / `Dispose`
|
2026-06-18 20:01:30 +08:00
|
|
|
|
|
|
|
|
|
|
#封装
|
|
|
|
|
|
#核心
|