【问题标题】:null GameObject becomes non-null when I convert to object当我转换为对象时,null GameObject 变为非 null
【发布时间】:2019-09-24 11:48:10
【问题描述】:

我有一个特殊的函数,它可以将object 作为参数。这样它既可以是 MonoBehaviour 也可以是 GameObject:

奇怪的是,当我将 null GameObject 转换为 Object 时,它不再显示为 null:

public class EquipmentSlots : MonoBehaviour {

  // This is null: it hasn't been set in the inspector
  public GameObject offHandEquipLocation;

  void Start () {
    Validation.RequireField(
      "offHandEquipLocation",
      offHandEquipLocation
    ) 
  }

}

public class Validation : MonoBehaviour {
  public static void RequireField(string name, object field) {
    if (field == null) {
      Debug.Log($"{name} field is unset");
    }
  }
}

Log 调用永远不会在这里运行,因为对象不为空。

如果我将Debug.Log(offHandEquipLocation == null) 放入Start() 函数中,它会打印出true。如果我将Debug.Log(field == null) 放在RequireField() 方法中,它会打印false

有没有办法可以查看object 是否为空?

【问题讨论】:

  • 不要使用== null,而是使用bool 运算符if(field)
  • @derHugo 无法做到这一点,因为fieldobject,因此不能以这种方式使用。我知道有解决方法,我真的只是想了解为什么我会看到这种行为。
  • @derHugo 我想看看是否有一种方法可以只更改 RequireField() 方法来完成这项工作,因为实际上有很多像这样的 Start() 函数,而且工作量更大全部更改。
  • 那你为什么将它传递为object?如果它是GameObject 类型,则使用Object (UnityEngine.Object) ... Unity 有overwritten 他bahvior 用于== null 类型Object(基本上所有Unity 内置类型)
  • 您可能对my answer here 感兴趣,关于确定为什么某个参考是null ;)

标签: c# unity3d


【解决方案1】:

对此的第一个想法是:不要这样做^^

这样做有一个问题:您更改了调用的堆栈跟踪。当您在控制台中看到错误时,通过将 null 检查传递给静态类,您无法直接看到它是从哪里抛出/记录的,直接通过双击它转到相应的代码行并且不能(很简单) 在层次结构中突出显示相应的对象“上下文”。

所以我总是尽可能地让检查接近实际使用情况,而不是像这样做

if(!offHandEquipLocation) Debug.LogError($"{nameof(offHandEquipLocation)} is not referenced!", this);

Debug.Log 的整体意义是让您在调试时更轻松;) 使用上述方法比使用您的方法您不必多次键入 Debug.LogError($"{nameof(offHandEquipLocation)} is not referenced!", this); 提供更多的优势。


正如其他人和我已经提到的那样,它不能按预期工作的原因是 Unity 对 Object 类型的 custom == null 行为,其中大多数内置类型尤其是 GameObjectScriptableObject 和 @ 987654334@继承。

即使Object“可能看起来是”或更好地说返回值等于 null的值,Unity实际上仍然在引用中存储一些元信息以便抛出自定义异常(MissingReferenceExceptionMissingComponentExceptionUnassignedReferenceException)比简单的NullReferenceException(你也可以看到here)更容易解释。因此,它实际上不是底层object 类型中的null

一旦您将其转换为object,自定义== null 行为就会消失,但底层object 仍然存在,因此您会得到field == null → false


但是,解决方案可能是使用bool operatorObject 创建一个简单的重载。这取代了null 检查并且意味着类似于This object is referenced, exists, and was not destroyed yet.。并继续使用== null 做其他事情

public static void RequireField(string name, object field) 
{
    if (field == null) Debug.LogError($"{name} field is unset");
}

public static void RequireField(string name, Object field) 
{
    if (!field) Debug.LogError($"{name} field is unset");
}

现在您可以同时使用两者。作为一个小提示:我不会将string 用于第一个参数,而是始终使用nameof 传递它以使其更安全地重命名

var GameObject someObject;
var string someString;

Validation.validate(nameof(someObject), someObject);
Validation.validate(nameof(someString), someString);

关于您使用它的论点的一点旁注,例如string 一般:

只要这是任何 Component (MonoBehaviour) 或 ScriptableObject 在 Inspector 中的 public[SerializedField] 字段,它总是由 Unity Inspector 本身使用默认值初始化。因此对任何一个进行空检查,例如

public int intValue;
[SerializeField] private string stringValue;
public Vector3 someVector;
public int[] intArray;
...

是多余的,因为它们都不会是null。这也适用于任何可序列化类型,因此即使您有自定义类

[Serializable]
public class Example
{
    public string aField;
}

然后在您的行为中使用序列化字段,例如

public List<Example> example;

这个也永远不会是null


顺便说一下,Validation 不应该继承自 MonoBehaviour,而应该是

public static class Validation
{
    // only static members
    ...
}

【讨论】:

  • 感谢您提供所有这些信息。考虑到这一点,我认为创建一个从我的游戏中获取任意字段并告诉我它是否为空的方法实际上是行不通的。我想我需要在调用该方法之前检查对象是否存在。
  • 我完全明白你从哪里来,最好简单明了,即使它有点重复/冗长。但是,我已经离那一点太远了。我的实际验证系统比我想象的还要复杂。
【解决方案2】:

这是因为 Unity 的 Objectdefines an == operator,如果您将 已销毁 实例与 null 进行比较,它将返回 true,即使引用实际上不是 null。通过使用offHandEquipLocation,您调用此运算符,而object field 调用.NET == operator

如前所述,您可以直接针对 GameObjects 调用 if,因为 Unity's Object defines an implicit conversion to bool,或者您可以在执行空检查之前强制转换为 System.Object

您还应该小心比较检查是否存在引用 null,如果您确实必须知道 nullref 和已销毁对象之间的区别,请使用 ReferenceEquals

【讨论】:

    【解决方案3】:

    这是因为 GameObject 的操作符 == 被覆盖了。这就是为什么gameObject == null 为真,但在转换为对象后,它不再为空。 见thisthis

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-15
      • 1970-01-01
      • 2018-02-10
      • 1970-01-01
      • 1970-01-01
      • 2020-04-09
      相关资源
      最近更新 更多