能否使用Func的Invoke方法返回对象数组?(游戏工具开发场景)
解决方案:收集多播委托所有返回值并减少内存分配
原代码核心问题
- 委托签名不匹配:
Func<int, int[]>要求接收int参数并返回int[],但你的RandomNumber是无参方法,编译会直接报错。 - 多播委托的局限:直接调用多播委托的
Invoke()只会返回最后一个注册方法的结果,无法收集所有订阅者的返回值。
实现思路
- 用
Func<int>作为订阅委托的类型(无参返回单个int),匹配取值方法签名。 - 通过遍历多播委托的
InvocationList,逐个调用订阅方法并收集结果。 - 加入缓存机制或对象池,减少频繁创建数组带来的内存分配,适配游戏开发的性能需求。
基础实现(带缓存)
using UnityEngine; using System; public class ValueCollector : MonoBehaviour { // 用event封装委托,限制外部只能通过订阅/取消订阅操作 private event Func<int> _valueProviders; // 缓存结果,仅在订阅变化时重新生成 private int[] _cachedValues; // 外部订阅接口 public void Subscribe(Func<int> valueProvider) { _valueProviders += valueProvider; // 订阅变更,清空缓存 _cachedValues = null; } // 外部取消订阅接口 public void Unsubscribe(Func<int> valueProvider) { _valueProviders -= valueProvider; _cachedValues = null; } // 获取所有订阅者的返回值数组 public int[] GetAllValues() { // 返回缓存结果,避免重复分配 if (_cachedValues != null) return _cachedValues; // 无订阅时返回空数组 if (_valueProviders == null) return Array.Empty<int>(); // 遍历所有订阅的方法,收集结果 var invocationList = _valueProviders.GetInvocationList(); _cachedValues = new int[invocationList.Length]; for (int i = 0; i < invocationList.Length; i++) { var provider = (Func<int>)invocationList[i]; _cachedValues[i] = provider.Invoke(); } return _cachedValues; } // 测试用取值方法 private int RandomNumber() { return UnityEngine.Random.Range(0, 10); } private void Start() { // 订阅多个取值方法 Subscribe(RandomNumber); Subscribe(() => 5); Subscribe(() => 15); // 获取并输出结果 var values = GetAllValues(); foreach (var num in values) { Debug.Log(num); } } }
进阶优化:用对象池减少内存分配
如果需要频繁调用GetAllValues(),可以用数组对象池复用数组,避免频繁创建销毁带来的GC:
using UnityEngine; using System; using System.Collections.Generic; public class ValueCollector : MonoBehaviour { private event Func<int> _valueProviders; // 数组对象池实现 private static readonly Queue<int[]> _arrayPool = new Queue<int[]>(); public void Subscribe(Func<int> valueProvider) { _valueProviders += valueProvider; } public void Unsubscribe(Func<int> valueProvider) { _valueProviders -= valueProvider; } public int[] GetAllValues() { if (_valueProviders == null) return Array.Empty<int>(); var invocationList = _valueProviders.GetInvocationList(); int length = invocationList.Length; // 从池子里租数组,没有则新建 int[] values = _arrayPool.Count > 0 && _arrayPool.Peek().Length >= length ? _arrayPool.Dequeue() : new int[length]; // 收集所有返回值 for (int i = 0; i < length; i++) { var provider = (Func<int>)invocationList[i]; values[i] = provider.Invoke(); } return values; } // 使用完数组后归还到池子里 public static void ReturnArray(int[] array) { if (array == null) return; _arrayPool.Enqueue(array); } private int RandomNumber() { return UnityEngine.Random.Range(0, 10); } private void Start() { Subscribe(RandomNumber); Subscribe(() => 5); var values = GetAllValues(); foreach (var num in values) { Debug.Log(num); } // 使用完归还数组 ReturnArray(values); } }
关键说明
- 模块化组织:通过
Subscribe/Unsubscribe接口,其他脚本可以灵活注册取值逻辑,实现解耦。 - 内存优化:缓存机制适合取值不频繁的场景,对象池适合高频调用场景,都能有效减少不必要的内存分配。
- 类型安全:用
event封装委托,避免外部直接修改委托列表,保证代码稳定性。
内容的提问来源于stack exchange,提问作者André Santana
相关产品推荐
相关产品推荐

