因为 sendmessage 性能差、不安全且功能受限:每次调用需字符串匹配和类型检查,不支持泛型、跨场景监听、编译期校验,易致 missingmethodexception 和内存泄漏;反射分发通过 type→delegate 映射提升性能与类型安全。

为什么不用 SendMessage 而要自己写反射分发?
因为 SendMessage 是 Unity 的反射调用,每次都要查方法名字符串、遍历组件、做类型匹配,实测在中等规模项目里调用 100 次/帧就明显拖帧;更麻烦的是它不支持泛型参数、不能跨场景保留监听、编译期完全无法检查方法是否存在——上线后某天你删了个回调函数,运行时才报 MissingMethodException,没人知道哪条消息断了。
- 它只认
GameObject上的MonoBehaviour,无法给纯 C# 管理器(比如数据模型、网络会话)发消息 - 无法区分同名方法重载,传参类型稍有偏差就静默失败
- 没有返回值支持,想同步获取处理结果只能绕路加回调委托
MessageDispatcher 怎么用反射注册 Handler?
核心是把“类型 → 处理函数”的映射关系存进字典,而不是靠字符串匹配或 switch。Protobuf 或自定义消息类都可作为 key:用 msg.GetType() 获取 Type 对象,再用 Delegate.CreateDelegate 绑定静态方法或实例方法。
- 注册时别直接传
MethodInfo,而是封装成Action<t></t>或Func<t task></t>委托,避免后续调用时反复反射 - 如果 handler 是实例方法,记得用
WeakReference包一层,否则容易造成监听者内存泄漏(尤其 UI 控件频繁销毁重建时) - 推荐加一层类型校验:分发前比对
msg.GetType() == handler.TargetType,防止误投到不兼容的 handler
泛型分发怎么避免装箱和类型擦除?
别用 object 当参数中转。C# 泛型在编译期生成具体方法,Action<loginrequest></loginrequest> 和 Action<logoutrequest></logoutrequest> 是两个完全不同的委托类型,运行时不会擦除——这是比字符串 topic 更安全、更快的路由方式。
- 定义统一入口:
public void Dispatch<t>(T msg) where T : class</t>,内部查_handlers[typeof(T)] - handler 注册必须显式指定泛型参数:
dispatcher.Register<loginrequest>(OnLogin)</loginrequest>,不能靠推导,否则泛型约束失效 - 如果消息体是 Protobuf 生成类,注意它们默认不支持无参构造,
Dispatch<t></t>内部 new 实例会失败,得改用Activator.CreateInstance(Type, args)或缓存构造器
反射分发在热更新或 AOT 环境下会崩吗?
会,而且很隐蔽。iOS 的 AOT 编译、Unity 的 IL2CPP、部分热更方案(如 HybridCLR)会裁剪未显式引用的泛型实例或委托类型。你写了 Register<playermove></playermove>,但没 anywhere new 过 Action<playermove></playermove>,发布包里这个委托类型可能根本不存在。
- 必须在初始化阶段主动“触达”所有要用的泛型组合,比如加个空的
PreloadHandlers()方法,里面调用一遍所有Register<xxx></xxx> - Protobuf 反射依赖
Descriptor元数据,确保 .proto 文件生成的代码没被 strip,且Google.Protobuf.Reflection.FileDescriptor被正确引用 - Android 上 R8 混淆可能把 handler 方法名改掉,需要在
proguard-user.txt里 keep 相关委托和处理方法
最麻烦的不是写不出来,而是它在编辑器里跑得好好的,一出包就收不到消息——这种问题得从构建流程开始卡点,不能等到测试才发现。










