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润色)

相关推荐
sali-tec29 分钟前
C# 基于halcon的视觉工作流-章56-彩图转云图
人工智能·算法·计算机视觉·c#
张人玉5 小时前
c#串口读写威盟士五插针
开发语言·c#·通讯
睡前要喝豆奶粉6 小时前
在.NET Core Web Api中使用redis
redis·c#·.netcore
偶尔的鼠标人7 小时前
SqlSugar查询字符串转成Int的问题
c#·sqlsugar
我不是程序猿儿7 小时前
【C#】WinForms 控件句柄与 UI 刷新时机
开发语言·ui·c#
!chen9 小时前
Unity颜色曲线ColorCurves
unity·游戏引擎
B0URNE9 小时前
【Unity基础详解】(4)Unity核心类:MonoBehaviour
unity·游戏引擎
聪明努力的积极向上11 小时前
【C#】HTTP中URL编码方式解析
开发语言·http·c#
关关长语13 小时前
(四) Dotnet中MCP客户端与服务端交互通知日志信息
ai·c#·mcp
小码编匠14 小时前
WPF 动态模拟CPU 使用率曲线图
后端·c#·.net