皮皮(Pipi)最近在写一款“满屏弹幕”的射击游戏。
为了记录每一发子弹的位置,他写了一个类:
publicclassPosition{publicfloat X;publicfloat Y;}每发射一颗子弹,他就 new Position()。 一秒钟发射一万颗,他就要 new 一万次。
结果是:
游戏每隔几秒钟就会严重卡顿一次,画面疯狂掉帧。
皮皮(砸键盘):
“为什么这么卡?!我的电脑可是顶配!”
瓜瓜(Guagua):
“因为你造了太多『重型垃圾』!”
每 new 一个 class:
当你一秒创建上万个对象时:
GC 启动↓Stop-The-World↓主线程暂停↓疯狂掉帧对于这种小巧的纯数据,你真正需要的是:
✅ 结构体(Struct)

结构体(struct)语法上几乎和 class 一模一样。
我们只需要:
- class+ structpublicstruct Position{publicfloat X;publicfloat Y;publicPosition(float x, float y) { X = x; Y = y; }publicvoidPrint() { Console.WriteLine($"坐标: ({X}, {Y})"); }}✔ 可以有字段✔ 可以有构造函数✔ 可以有方法✔ 甚至可以实现接口
皮皮疑惑:
“既然长得一样,那它凭什么不卡?”
这其实就是我们在:
👉《C# 学习笔记 02:变量与数据类型,以及最容易搞混的“值”与“引用”》
以及:
👉《C# 学习笔记番外篇:魔法背包与传送门 —— Ref / Out & Tuples》
里学过知识的 终极应用场景。
当你 new 一个对象:
变量里存的是:内存地址(引用)真正数据在:Heap(堆)生命周期:GC决定你传参时传的是:
🔑 一把钥匙(地址)
当你创建一个结构体:
变量里存的是:数据本体通常存放在:Stack(栈)生命周期:作用域结束立即释放你传参时传的是:
📄 一份复印件
对于:
这种:
✔ 数量巨大✔ 生命周期极短✔ 纯数据打包
用 class = GC灾难用 struct = CPU缓存友好 + 零GC压力
皮皮把 Position 改成 struct 后:
游戏不卡了 ✅但子弹却不动了 ❌
publicvoidMoveBullet(Position p){ p.X += 10;}Position myPos = new Position(0, 0);MoveBullet(myPos);Console.WriteLine(myPos.X);// 输出:0 因为 Struct 是:
值传递
你传进去的只是:
📄 坐标复印件
方法里改的不是原始数据。
publicvoidMoveBullet(ref Position p){ p.X += 10;}Struct 的最佳实践其实是:
❗ 不要修改它,而是创建新值
public Position MoveBullet(Position p){returnnew Position(p.X + 10, p.Y);}myPos = MoveBullet(myPos);这叫:
Immutable Struct(不可变结构体)
微软官方建议:
只有 全部满足以下条件 才考虑使用 Struct:
这也是为什么 Unity 里你不能这么写:
transform.position.x = 1; 而必须:
transform.position = new Vector3(1,1,1); 因为:
Vector3 是 Structposition 返回的是一份复印件皮皮的游戏越来越复杂。
玩家死亡时需要:
如果全写在一个方法里:
💀 几百行 if else 地狱
下一期我们学习:
🎯 委托与事件(Delegates & Events)
让代码学会:
✔ 发通知✔ 做订阅✔ 解耦模块
正式迈入:
架构级编程思维