F# null 测试无法检测到空值

F# null test fails to detect null values

在 F# 中可靠地测试 null 的正确方法是什么?

我有一个构建在 Unity 游戏引擎(这是一个闭源单声道 C#/C++ 引擎)之上的 F#/C# 混合项目。

我有一个调用 Unity API 函数的 F# 函数,该函数可能 return 为空。 Unity 函数 return 为空,但我的 F# 代码无法检测到此 (我从测试数据的形状、附加调试器、插入日志语句等方面都知道这一点) .我写的每一个 null 测试似乎 returns false 而它应该是 true。第一次尝试:

let rec FindInParents<'t when 't : null> (go : GameObject) = 
    match go with 
    | null -> null
    | _ ->
        let comp = go.GetComponent<'t>() // GetComponent returns null
        match comp with
        | null -> FindInParents (go.transform.parent.gameObject) // This should be matched but isn't
        | _ -> comp // Always this branch

我也尝试了以下方法但没有成功:

let rec FindInParents<'t when 't : null> (go : GameObject) = 
    if obj.ReferenceEquals (go, Unchecked.defaultof<'t>) then null 
    else 
        let comp = go.GetComponent<'t>() // Comp is null
        if obj.ReferenceEquals (comp, Unchecked.defaultof<'t>) then FindInParents<'t> (go.transform.parent.gameObject)
        else comp // Always this branch

我觉得我在这里遗漏了一些基本的东西,但到目前为止我还没有明白。有什么指点吗?

编辑: 我还应该指出,GetComponent 总是 return 是 UnityEngine.Component 的子类型,并且始终是引用类型。 UnityEngine.Component 是 UnityEngine.Object 的子类型,它定义了一个自定义 == 运算符(我认为这不相关,因为在第二个示例中不应调用 == (请参阅 Daniel 对 [Handling Null Values in F#)

事实证明,Unity 对已在非托管端销毁但尚未在托管端收集的对象使用伪造的空值。自定义 == / != 运算符检查假空值。

对于问题中的通用函数,F# 将使用 IL 指令进行空值测试 (brfalse.s) - 这显然不会检测到 Unity 假空值。对 null 的测试显式导致对 LanguagePrimitives.HashCompare.GenericEqualityIntrinsic 的调用,它也不知道 Unity 伪空值。

解决方法是在unity对象上调用Equals,确保调用重载的Unity运算符:

let isUnityNull x = 
    let y = box x // In case of value types
    obj.ReferenceEquals (y, Unchecked.defaultof<_>) || // Regular null check
    y.Equals(Unchecked.defaultof<_>) // Will call Unity overload if needed