Unity反射调用 ReactiveProperty<T>(泛型类型)内部方法时崩溃

Unity Editor 崩溃问题分析与修复记录:反射调用 ReactiveProperty 时崩溃


一、问题概述

在实现 ViewModel ↔ Model 双向绑定 时,

使用反射调用 ReactiveProperty<T>.Subscribe(Action<T>) 并同步 Value 时,
Unity 编辑器直接崩溃(非异常抛出)

崩溃发生在以下场景:

  • 泛型类型:ReactiveProperty<TEnum>

  • 使用反射订阅:

    csharp 复制代码
    SubscribeMethod.Invoke(null, new object[] { reactiveProp, action });
  • 使用反射给枚举类型属性赋值:

    csharp 复制代码
    propertyInfo.SetValue(target, value);

Unity 控制台没有任何异常提示,只提示:

复制代码
A crash has been intercepted by the crash handler.
See crash report in: C:/Users/.../AppData/Local/Temp/Unity/Editor/Crashes

二、崩溃堆栈(关键段)

复制代码
mono_object_handle_isinst
ves_icall_RuntimeTypeHandle_IsInstanceOfType_raw
System.RuntimeType:IsInstanceOfType (object)
System.Reflection.RuntimePropertyInfo:SetValue (object,object)
EnumDescriptorViewModel/<>c__DisplayClass13_0:<.ctor>b__0 (object)
R3.ReactiveProperty`1<TEnum>:set_Value (TEnum)

结论:

Unity 内部在 PropertyInfo.SetValue 时,Mono 的类型检查逻辑访问了空指针。

这是 Mono 特有的"类型句柄误判"问题。


三、问题根源分析

根本原因

  1. Mono 的反射类型检查存在 Bug

    当传入的对象是一个 从泛型参数推断出的枚举值 时(例如 TEnum),

    Mono 运行时在执行 IsInstanceOfType 时无法正确识别类型句柄。

  2. Delegate.CreateDelegate 装箱转换隐患

    原始委托使用了 Action<object> 绑定到 Action<TEnum>

    在某些 Unity/Mono 版本下,这会触发内部类型适配逻辑错误。

这两个问题叠加,最终导致:

⚠️ Mono 在 Editor 模式中访问了一个无效的对象句柄,从而直接崩溃。


四、最小复现示例

csharp 复制代码
public enum AxisType { X, Y, Z }

public class Demo
{
    public ReactiveProperty<AxisType> Model = new ReactiveProperty<AxisType>(AxisType.X);

    public Demo()
    {
        var vm = new ReactiveProperty<AxisType>(AxisType.X);

        // 反射绑定(示例)
        var prop = typeof(ReactiveProperty<AxisType>).GetProperty("Value");

        // 此行会导致 Unity 崩溃(非异常)
        prop.SetValue(Model, vm.Value);
    }
}

五、解决方案

修复点 1:在 SetValue 前调用 Enum.ToObject

csharp 复制代码
if (property.PropertyType.IsEnum && value != null)
{
    // 防止 Mono 类型识别错误
    value = Enum.ToObject(property.PropertyType, value);
}
property.SetValue(target, value);

解释:

重新封装枚举值为目标类型对象,确保 Mono 在运行时能识别类型。


修复点 2:使用 Expression 生成强类型委托

csharp 复制代码
var param = Expression.Parameter(elementType, "x");
var body = Expression.Invoke(Expression.Constant(onNext), Expression.Convert(param, typeof(object)));
var lambda = Expression.Lambda(actionType, body, param);
var action = lambda.Compile();

解释:

Expression 创建出的委托是真正的 Action<TEnum> 类型,

避免了跨类型装箱拆箱带来的潜在崩溃。


六、完整的安全封装示例

csharp 复制代码
public static class SafeSubscribeUtility
{
    public static IDisposable SubscribeSafe(object reactiveProp, Action<object> onNext)
    {
        var type = reactiveProp.GetType();
        var elementType = type.GetGenericArguments()[0];

        // 构造强类型 Action<T>
        var param = Expression.Parameter(elementType, "x");
        var body = Expression.Invoke(Expression.Constant(onNext), Expression.Convert(param, typeof(object)));
        var lambda = Expression.Lambda(typeof(Action<>).MakeGenericType(elementType), body, param);
        var action = lambda.Compile();

        // 反射调用扩展方法 Subscribe<T>(Action<T>)
        var method = typeof(ObservableSubscribeExtensions)
            .GetMethods(BindingFlags.Static | BindingFlags.Public)
            .First(m => m.Name == "Subscribe" && m.GetParameters().Length == 2)
            .MakeGenericMethod(elementType);

        return (IDisposable)method.Invoke(null, new object[] { reactiveProp, action });
    }

    public static void SetEnumValueSafe(PropertyInfo property, object target, object value)
    {
        if (property.PropertyType.IsEnum && value != null)
            value = Enum.ToObject(property.PropertyType, value);
        property.SetValue(target, value);
    }
}

七、经验总结

问题 根源 修复手段
PropertyInfo.SetValue 崩溃 Mono 识别枚举类型句柄错误 Enum.ToObject 重新封箱
Delegate.CreateDelegate 不稳定 Action → Action 装箱 用 Expression.Lambda 创建强类型委托
Unity Editor 崩溃无异常 Mono native 崩溃,无托管异常 加入类型显式转换保障

八、延伸建议

  • 尽量避免 Editor 下频繁使用反射绑定 ReactiveProperty;
  • 若需运行时动态绑定,请封装在工具类中统一使用;
  • 如果可能,使用源码生成(如 Source Generator)代替反射。

一句话总结:

"Unity 崩溃并不是逻辑错误,而是 Mono 在处理反射泛型枚举赋值时的类型句柄 Bug。

通过 Enum.ToObject + 强类型委托修正,就能彻底规避此问题。"

(本文由本人撰写,ChatGPT润色)

相关推荐
开发游戏的老王3 小时前
虚幻引擎虚拟制片入门教程 之 模型资源的导入
java·游戏引擎·虚幻
CodeCraft Studio4 小时前
如何从 FastReport .NET 将报表导出为 JPEG / PNG / BMP / GIF / TIFF / EMF
windows·.net·报表开发·报表工具·fastreport·报表转图片
缺点内向6 小时前
C# 中 Excel 工作表打印前页面边距的设置方法
c#·.net·excel
喵叔哟7 小时前
6. 从0到上线:.NET 8 + ML.NET LTR 智能类目匹配实战--渐进式学习闭环:从反馈到再训练
学习·机器学习·.net
向宇it8 小时前
【推荐100个unity插件】将您的场景渲染为美丽的冬季风景——Global Snow 2
unity·游戏引擎·风景
浅丿忆十一8 小时前
关于unity一个场景中存在多个相机时Game视图的画面问题
unity·游戏引擎
雪芽蓝域zzs14 小时前
uniapp AES 加密解密
开发语言·uni-app·c#
WLJT12312312317 小时前
方寸之间见天地:新兴高端印章的当代破局与价值重构
unity·游戏引擎
weixin_4569042719 小时前
C# 中的回调函数
java·前端·c#