重置对象后如何将缓存的委托重新添加至事件?
问题描述
我有一个包含事件的对象,希望将其重置为默认状态但保留现有事件订阅。预期流程是:让多个订阅者订阅_myRepo的事件 → 将_myRepo设为null → 创建默认状态的新实例 → 恢复之前保存的所有订阅者。由于类中有多个仓库,采用泛型而非强类型实现。
当前遇到的问题:对象重新创建后,无法在ApplyCachedEventHandlers()函数中将订阅重新添加到事件中,请问如何将委托重新绑定到新对象的事件上?
现有代码实现
private IMyRepo _myRepo; private Dictionary<Type, Delegate> _subscribers = new Dictionary<Type, Delegate>(); public IMyRepo MyRepo { get { if (_myRepo == null) _myRepo = new MyRepo(_transaction); ApplyCachedEventHandlers(_myRepo); return _myRepo; } } private void ResetRepo() { CacheEventHandlers(_myRepo); _myRepo = null; } private void CacheEventHandlers<T>(T repo) { if (repo == null) return; Type t = repo.GetType(); FieldInfo[] fia = t.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic); foreach (FieldInfo fi in fia) { Delegate d = fi.GetValue(repo) as Delegate; if (d != null) { foreach (Delegate subscriber in d.GetInvocationList()) _subscribers.Add(t, subscriber); } } } private void ApplyCachedEventHandlers<T>(T repo) { if (repo == null || _subscribers == null || _subscribers.Count < 1) return; Type t = repo.GetType(); FieldInfo[] fia = t.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic); foreach (KeyValuePair<Type, Delegate> subscriber in _subscribers) { if (t == subscriber.Key) { foreach (FieldInfo fi in fia) { // 此处无法完成委托绑定 Debug.WriteLine("Add the saved delegate to the subscriptions of the event (fi)."); } } } }
解决方案
核心问题修正
- 缓存字典结构错误:原代码用
Type作为键,会导致同一类型的多个委托被覆盖(字典键唯一),需改为按「仓库类型+事件字段名」区分,保存每个事件对应的订阅委托列表。 - 事件匹配逻辑缺失:缓存和恢复时未关联具体事件字段,导致无法对应到新对象的正确事件。
修正后代码
调整缓存字典结构
private IMyRepo _myRepo; // 键:(仓库类型, 事件字段名);值:该事件的所有订阅委托 private Dictionary<Tuple<Type, string>, List<Delegate>> _eventSubscribers = new Dictionary<Tuple<Type, string>, List<Delegate>>();
修正缓存事件方法
private void CacheEventHandlers<T>(T repo) { if (repo == null) return; Type repoType = repo.GetType(); // 仅筛选事件对应的Delegate类型字段 FieldInfo[] eventFields = repoType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) .Where(f => typeof(Delegate).IsAssignableFrom(f.FieldType)) .ToArray(); _eventSubscribers.Clear(); // 重置缓存避免残留旧数据 foreach (FieldInfo field in eventFields) { Delegate currentDelegate = field.GetValue(repo) as Delegate; if (currentDelegate == null) continue; // 提取该事件的所有订阅委托 var subscribers = currentDelegate.GetInvocationList().ToList(); var key = Tuple.Create(repoType, field.Name); _eventSubscribers[key] = subscribers; } }
修正恢复事件方法
private void ApplyCachedEventHandlers<T>(T repo) { if (repo == null || _eventSubscribers.Count == 0) return; Type repoType = repo.GetType(); FieldInfo[] eventFields = repoType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic) .Where(f => typeof(Delegate).IsAssignableFrom(f.FieldType)) .ToArray(); foreach (FieldInfo field in eventFields) { var key = Tuple.Create(repoType, field.Name); if (_eventSubscribers.TryGetValue(key, out var subscribers)) { Delegate currentEvent = field.GetValue(repo) as Delegate; // 逐个合并缓存的委托到新对象的事件中 foreach (Delegate subscriber in subscribers) { currentEvent = Delegate.Combine(currentEvent, subscriber); } // 将合并后的委托赋值回事件字段 field.SetValue(repo, currentEvent); } } }
保留原有调用逻辑
private void ResetRepo() { CacheEventHandlers(_myRepo); _myRepo = null; } public IMyRepo MyRepo { get { if (_myRepo == null) _myRepo = new MyRepo(_transaction); ApplyCachedEventHandlers(_myRepo); return _myRepo; } }
额外注意事项
- 确保事件是标准.NET事件(用
event关键字声明),底层字段名需与反射匹配; - 泛型方法可添加
where T : class约束,避免值类型无效调用; - 重置仓库时必须先缓存再置空对象,防止丢失订阅数据。
内容的提问来源于stack exchange,提问作者Arvo Bowen
相关产品推荐
相关产品推荐

