Files
My-Notes/笔记/CSharp/核心/构造函数、析构函数、垃圾回收.md
T
2026-06-18 20:01:30 +08:00

246 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 构造函数:
概念:
实例化时,用于初始化的函数
不写默认有一个无参构造函数
==可以重载==
通常用法:
自定义初始化规则
语法:
访问修饰符 类名(){}
案例:
```Csharp
class Person()
{
public string name;
public int age;
public Person()
{
name = "默认值";
age = 默认值;
}
}
```
this关键字:
this指当前类声明的成员变量
```Csharp
class Person()
{
public string name;
public int age;
public Person()
{
name = "默认值";
age = 默认值;
}
public Person(int age,string name) //重构
{
this.name = name;
//后面的name是传递的参数,this.name是最上方public int出来的
this.age = name;
}
}
```
特殊写法:
通过this重用构造函数代码,即先调用无参构造函数,然后在调用这个特殊写法的构造函数
语法:访问修饰符 构造函数名(参数列表):this(【可选】参数1,参数2.....)
例子:
```Csharp
public Person(int age, string name)thisage
{
balabala
}
```
过程:先调用无参Person构造函数,然后寻找只有构造单个参数age的构造函数进行初始化
注意:
==函数名和类名必须相同==
==没有特殊用途,一般都是public==
==如果写了有参构造函数且自己没有保留无参构造函数,那么类在实例化时只能使用有参构造函数且无法使用无参构造函数==
----
# 析构函数
概念:
当引用类型堆内存被回收时,会调用该函数
对于需要手动管理内存的语言(C++),需要在析构函数中做一些内存回收处理
C#有自动垃圾回收机制GC
Unity中基本不用管
通常用法:
语法:
```Csharp
~类名()
{
}
```
案例:
```Csharp
~Person()
{
}
```
注意:
----
# 垃圾回收
垃圾回收(GC
通过遍历堆Heap上动态分配的所有对象
通过识别他们是否被引用来确定哪些对象时垃圾,哪些对象仍然需要引用
垃圾:没有变量或对象引用的内容
如果是垃圾就要被回收释放
一般都是==被动触发(会造成卡顿)==,所以游戏一般需要手动执行垃圾回收
语法:GC.Collect();
==GC会造成卡顿,所以在加载Loading时一般会调用==
垃圾回收有多种算法
引用计数
标记清楚
标记整理
复制集合
注意:
GC只负责Heap内存的垃圾祸首
引用类型都是在Heap中的,所以他需要GC来进行管理
栈Stack上的内存时系统自动管理的
所以不需要GC
C#中垃圾回收机制的大概原理
他会把内存分为012代内存
代的概念是垃圾回收中的一种算法需要
新分配的对象都会被配置在第0代内存中
每次分配都可能会进行垃圾回收(一般来说0代内存满了就会触发)
在第一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,会进行一下两部
1.标记对象,从根(静态参数、方法参数)开始检查引用对象,标记后为可达对象,未标记就是不可大对象
不可达对象就会认为是垃圾
2.搬迁对象 压缩堆 (挂起执行内存托管代码线程:把第0代内存(还不是垃圾的内存)放到一代进行搬迁)释放未标记的对象、搬迁可达对象、修改引用地址
大对象总被认为是二带内存,目的是减少性能损耗
不会对大对象进行压缩搬迁 大对象是85000kb以上的
示意图
step1:程序放入内存
![[Pasted image 20260412210327.png|697]]
step2:发现沾满了
![[Pasted image 20260412210704.png]]
step3:原来的地址搬迁,无引用的垃圾丢掉
![[Pasted image 20260412210810.png]]
step4:D放入
![[Pasted image 20260412210946.png]]
step5:如果0代1代后续又满了,那么这套算法会同时做用在两代内存中,以此类推
类似多级缓存cache,L1最快但常换L3最慢
#基础
# GC和IDispose()
### 首先区分资源类型:
- **托管资源**:C# 自己管理的内存(比如普通对象、字符串)
- 不用你管,**垃圾回收器(GC)会自动释放**
- **非托管资源**:C# 管不了的外部资源
- 例子:文件流、数据库连接、网络套接字、图片句柄、系统内核对象
- **必须手动释放**,否则会造成**资源泄漏**(程序越跑越慢、卡死)
### 为什么要用 `IDisposable`
- 非托管资源 GC 不会自动清理
- 必须提供一个**标准、统一**的方式手动释放
- `IDisposable` 就是 .NET 专门定义的**释放资源标准接口**
### 标准释放模式
例子:
```csharp
using System;
// 1. 实现 IDisposable 接口
public class MyResource : IDisposable
{
// 标记资源是否已释放
private bool _disposed = false;
// 2. 公共释放方法(外部调用)
public void Dispose()
{
Dispose(true);
// 告诉GC:不用再管我了,我已经手动释放完了
GC.SuppressFinalize(this);
}
// 3. 核心释放逻辑
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// 释放 托管资源
}
// 释放 非托管资源(比如关闭文件、关闭数据库连接)
// 这里写你的资源释放代码
_disposed = true;
}
// 4. 析构函数:防止用户忘记调用 Dispose
~MyResource()
{
Dispose(false);
}
}
```
##### 解释:
逻辑:
1. 用完实例化对象
2. 调用`.Dispose()`手写的`.Close()`等方法进行手动释放
3. 判断标记变量
4. 没释放就执行释放代码
5. 标记为`true`
`GC.SuppressFinalize(this);` 的作用
告诉.NET的GC这个对象的非托管资源,我**手动释放完了**,你不需要在垃圾回收的时候,再执行它的析构函数了。==它不会取消GC运行,只是取消执行析构函数!==
析构函数 `~MyResource()` 是干嘛的?和 GC 啥关系?
防止你忘记调用 `Dispose()`
场景模拟
1. 你写了代码,**马虎忘了**调用 `.Dispose()`
2. 非托管资源(文件 / 数据库)一直占着没释放
3. 等到 GC(清洁工)来回收垃圾时
4. **自动触发析构函数** `~MyResource()`
5. 函数里执行 `Dispose(false)`,强行把非托管资源释放了
==一个实例化对象dispose后就不会再用了(设计上就是如此,用过dispose后标签一直是true==
==而且由于它dispose了,内部引用是空的,所以只会留下一个实例化的小壳==
==实例化的小壳也会因为没有被引用被GC清理,不会造成太大的性能问题==
如果需要==一次性==彻底释放
例子:
```Csharp
var resource1 = new MyResource();
resource1.Dispose();
//手动切断引用
resource1 = null;
//手动触发GC
GC.Collection();
GC.WaitForPendingFinalizers();//等待回收完成
```
#封装
#核心