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

如何让实现接口的结构体注册事件回调时避免装箱?

问题描述

背景

我希望让类实例向事件系统注册回调,同时避免装箱操作。

自研的事件系统结构如下:

class EventSystem
{
    // 为事件注册结构体实例
    // 事件触发时调用Run()方法并传入信息
    public void Register(ICallback callbackStruct, Event e);
}

public enum Event
{
    SOME_EVENT,
    SOME_OTHER_EVENT
}

interface ICallback
{
   void Run(string data);
}

示例实现:

class Example
{
    private Behaviour_A a = new Behaviour_A();
    private Behaviour_B b = new Behaviour_B();

    public Example()
    {
        eventSystem.Register(a, SOME_EVENT);
        eventSystem.Register(b, SOME_OTHER_EVENT);
    }

    struct Behaviour_A : ICallback
    {
        void Run(string data)
        {
            // 自定义逻辑
        }
    }

    struct Behaviour_B : ICallback
    {
        void Run(string data)
        {
            // 另一套自定义逻辑
        }
    }
}

现有方案优势

  • 采用组合而非继承,支持单个实例为同一事件注册多种回调行为
  • 结构体相比匿名函数具备状态性,可复用不同参数

现有方案劣势

每次传递结构体到Register方法时都会触发装箱操作,而我需要尽可能避免堆内存分配。

核心问题

有没有办法在保留结构体自定义行为的同时,避免装箱操作?


解决方案

方法1:泛型约束改造事件系统(推荐,支持有状态结构体)

通过为Register方法添加泛型参数,并约束为实现ICallback的结构体,CLR会为每个具体的结构体类型生成专属的方法版本,直接操作值类型实例,完全避免装箱。

改造后的EventSystem实现:

class EventSystem
{
    private Dictionary<Event, ICallbackContainer> _eventCallbacks = new Dictionary<Event, ICallbackContainer>();

    public void Register<T>(T callbackStruct, Event e) where T : struct, ICallback
    {
        if (!_eventCallbacks.TryGetValue(e, out var container))
        {
            container = new CallbackContainer<T>();
            _eventCallbacks[e] = container;
        }

        ((CallbackContainer<T>)container).Callbacks.Add(callbackStruct);
    }

    public void TriggerEvent(Event e, string data)
    {
        if (_eventCallbacks.TryGetValue(e, out var container))
        {
            container.Invoke(data);
        }
    }

    // 抽象容器接口,统一触发逻辑
    private interface ICallbackContainer
    {
        void Invoke(string data);
    }

    // 泛型容器,存储特定结构体类型的回调列表
    private class CallbackContainer<T> : ICallbackContainer where T : struct, ICallback
    {
        public List<T> Callbacks { get; } = new List<T>();

        public void Invoke(string data)
        {
            foreach (var callback in Callbacks)
            {
                callback.Run(data);
            }
        }
    }
}

调用时直接传入结构体实例即可,无需装箱:

eventSystem.Register(a, SOME_EVENT); // a是Behaviour_A结构体实例,无装箱

方法2:委托+结构体静态方法(适用于无状态回调)

如果你的结构体不需要维护状态,可以直接将静态方法包装成Action<string>委托注册,同样不会产生装箱:

class EventSystem
{
    private Dictionary<Event, List<Action<string>>> _eventCallbacks = new Dictionary<Event, List<Action<string>>>();

    public void Register(Action<string> callback, Event e)
    {
        if (!_eventCallbacks.TryGetValue(e, out var list))
        {
            list = new List<Action<string>>();
            _eventCallbacks[e] = list;
        }
        list.Add(callback);
    }

    public void TriggerEvent(Event e, string data)
    {
        if (_eventCallbacks.TryGetValue(e, out var list))
        {
            foreach (var callback in list)
            {
                callback(data);
            }
        }
    }
}

// 定义无状态结构体
struct Behaviour_A
{
    public static void Run(string data)
    {
        // 自定义逻辑
    }
}

// 注册方式
eventSystem.Register(Behaviour_A.Run, SOME_EVENT);

但这种方式不支持有状态的结构体,仅适合无状态场景。

方法3:值类型列表优化存储

第一种泛型方案已经通过List<T>直接存储结构体实例,所有操作都在值类型层面进行,完全避免了装箱。如果需要更极致的内存控制,还可以使用Span<T>或Memory<T>来管理回调列表,但对于大多数场景,List<T>已经足够高效。


内容的提问来源于stack exchange,提问作者Jam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 07:35:16