You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重置对象后如何将缓存的委托重新添加至事件?

问题描述

我有一个包含事件的对象,希望将其重置为默认状态但保留现有事件订阅。预期流程是:让多个订阅者订阅_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).");
            }
        }
    }
}

解决方案

核心问题修正

  1. 缓存字典结构错误:原代码用Type作为键,会导致同一类型的多个委托被覆盖(字典键唯一),需改为按「仓库类型+事件字段名」区分,保存每个事件对应的订阅委托列表。
  2. 事件匹配逻辑缺失:缓存和恢复时未关联具体事件字段,导致无法对应到新对象的正确事件。

修正后代码

调整缓存字典结构

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 17:17:42