null GameObject становится ненулевым, когда я конвертирую в объект - PullRequest
2 голосов
/ 23 сентября 2019

У меня есть особая функция, которая может принимать object в качестве аргумента.Это может быть MonoBehaviour или GameObject:

Странно то, что когда я конвертирую ноль GameObject в Object, он больше не отображается как ноль:

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 нулевым?

Ответы [ 3 ]

3 голосов
/ 23 сентября 2019

Самая первая мысль об этом: Не делайте этого ^^

При этом есть одна проблема: вы изменяете трассировку стека вызова.Передав нулевую проверку статическому классу, когда вы видите ошибку в консоли, которую вы не можете любить, прежде чем непосредственно увидеть, где она была выброшена / записана, сразу перейдите к соответствующей строке кода, дважды щелкнув по ней и не может (это легко) выделите соответствующий объект «контекст» в иерархии.

Таким образом, я бы всегда держал чеки как можно ближе к фактическому использованию и скорее делал бы это как

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

Весь смысл Debug.Log облегчает вашу жизнь при отладке;) Использование вышеуказанного подхода дает гораздо больше преимуществ, чем тот факт, что при использовании этого подхода вам не нужно вводить Debug.LogError($"{nameof(offHandEquipLocation)} is not referenced!", this); несколько раз.


Как и другие, я уже упоминал причину, по которой это не так.Работает так, как вы ожидаете, это пользовательское == null поведение Unity для типа Object, из которого большинство встроенных типов, в частности GameObject, ScriptableObject и Componentнаследовать.

Даже если Object "может показаться" или, лучше сказать, возвращает значение , равное значению null Unity фактически сохраняетнекоторая метаинформация в справочнике для создания пользовательских исключений (MissingReferenceException, MissingComponentException, UnassignedReferenceException), которые более понятны, чем простые NullReferenceException (как вы также можете увидеть здесь ),Следовательно, на самом деле это не null в базовом типе object.

Как только вы преобразуете его в object, пользовательское поведение == null исчезнет, ​​но базовое object все еще существует и, следовательно,вы получаете field == nullfalse


Однако решение может заключаться в создании простой перегрузки с использованием оператора bool для Object.Это заменяет проверку 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);

Небольшой sidenoteотносительно вашего аргумента для использования этого, например, string в целом:

Как только это поле будет public или [SerializedField] в Инспекторе для любого Component (MonoBehaviour) или ScriptableObject он всегда инициализируется самим инспектором Unity со значением по умолчанию.Следовательно, нулевая проверка любого из, например,

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 голосов
/ 23 сентября 2019

Это потому, что Unity Object определяет == оператор , который вернет true, если вы сравните уничтоженный экземпляр с null, даже если ссылка не указана.на самом деле null.Используя offHandEquipLocation, вы звоните этому оператору, а object field вызывает оператора .NET == .

Как уже было сказано, вы можете напрямую позвонить на ifпротив GameObjects, поскольку Unity Object определяет неявное преобразование в bool, или вы можете привести к System.Object перед выполнением нулевой проверки.

Вы также должны быть осторожны при сравнениипроверяя ссылочный ноль и используйте ReferenceEquals, если вы действительно должны знать разницу между нуль-ссылкой и уничтоженным объектом.

1 голос
/ 23 сентября 2019

Это потому, что оператор GameObject == переопределен.Вот почему gameObject == null верно, но после приведения к объекту оно больше не равно нулю.См это и это

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...