享元模式核心是严格分离intrinsicstate与extrinsicstate,并用concurrentdictionary线程安全缓存共享实例;漏任一环节则内存不降、性能反损。

直接说结论:C#中实现享元模式,核心不是“写个接口+类”,而是**严格分离 intrinsicState(内部状态)和 extrinsicState(外部状态),并用线程安全的工厂缓存共享实例**。漏掉任一环节,内存不降、性能反损。
为什么 FlyweightFactory 必须用 ConcurrentDictionary 而非 Dictionary
多线程环境下,多个线程同时调用 GetFlyweight() 极易触发重复创建——Dictionary 的 ContainsKey + Add 非原子操作,会生成冗余对象,彻底破坏享元本意。
- 用
ConcurrentDictionary<tkey tvalue>.GetOrAdd()</tkey>替代手动判断+插入,天然线程安全 - 键必须能唯一标识内部状态,比如
$"{type}_{texture}_{layer}",不能只用type - 若内部状态含引用类型(如
Brush或Font),需确保它们本身是线程安全或不可变的,否则共享反而引发渲染错乱
ConcreteFlyweight 里不能存任何可变字段
享元对象一旦被共享,就绝不能在运行时修改其内部字段——否则一个客户端改了 _color,所有用它的地方都跟着变。
- 所有内部状态字段必须是
readonly或init,构造时一次性赋值 - 禁止在
Operation()方法里修改this的任何字段;外部状态(如x,y,size)只能作为参数传入,绝不存为成员 - 常见错误:把
position当成内部状态塞进GrassTerrain类,结果地图上所有草地都跑到同一个坐标
什么时候不该硬套享元模式
享元不是银弹。它解决的是「大量对象、高内存压力、状态可拆分」三者同时成立的问题。其他情况强行套用,代码更复杂,性能还更差。
- 对象总数
- 内部状态组合爆炸(比如 5 个字段,每个有 20 种取值 → 3.2M 种键)?缓存失效率高,
ConcurrentDictionary查找开销反超 new 开销 - 外部状态传递成本高(比如每次
Render()都要传入 10 个struct参数)?考虑用对象池(ObjectPool<t></t>)替代,更轻量
最易被忽略的一点:享元对象的生命周期由工厂管理,但外部状态的生命周期完全独立。如果客户端缓存了某个享元实例,又长期持有过期的外部状态(比如已销毁的 UI 控件坐标),Draw() 就会静默失败——这种 bug 不报异常,只渲染错位,排查成本极高。










