Что именно возвращает GetComponent?

GetComponent в Unity ведёт себя по‑разному для встроенных компонентов движка и ваших собственных MonoBehaviour. В Редакторе отсутствующие компоненты движка, вроде Renderer, могут возвращать особый «псевдо‑null» объект с переопределённым оператором ==, тогда как в Standalone это настоящий null; для ваших собственных типов в обоих случаях возвращается реальный null. В посте мы разбираем логи и объясняем, почему так (и чем отличается TryGetComponent), чтобы вы могли писать надёжные проверки на null в разных окружениях.

Jun 5, 2022

Привет, друзья Unity. Уверен, вы используете GetComponent<T>() каждый день. Но задумывались ли вы, что именно он возвращает? Давайте разбираться!

Представим ситуацию: у нас есть два класса, производных от MonoBehaviour: MyClassA и MyClassB. В сцене есть GameObject, и единственный скрипт, прикреплённый к этому GameObject, — это MyClassA — больше ничего, ни других компонентов, ни скриптов.

Теперь попытаемся вызвать GetComponent<MyClassB>() и GetComponent<Renderer>() из MonoBehaviour MyClassA. Это выглядит так:

using UnityEngine;

namespace GetComponentExample
{
    public class MyClassA : MonoBehaviour
    {
        private void Start()
        {
            //Some class nested from MonoBehaviour
            //Or HingeJoint, Or MeshFilter, etc.
            var notExistingMonobehaviour = GetComponent<MyClassB>();    
            var notExistingRenderer = GetComponent<Renderer>();         

            Debug.LogWarning(notExistingMonobehaviour == null);
            Debug.LogWarning((object)notExistingMonobehaviour == null);
            Debug.LogWarning(notExistingRenderer == null);
            Debug.LogWarning((object)notExistingRenderer == null);

            TryGetComponent<MyClassB>(out var stillNotExistingMonobehaviour);
            TryGetComponent<Renderer>(out var stillNotExistingRenderer);

            Debug.LogWarning(stillNotExistingMonobehaviour == null);
            Debug.LogWarning((object)stillNotExistingMonobehaviour == null);
            Debug.LogWarning(stillNotExistingRenderer == null);
            Debug.LogWarning((object)stillNotExistingRenderer == null);
        }
    }
}

Вопрос: что будет выведено в Debug.Log() в Редакторе и в Standalone‑сборке — и почему? Немного подумайте, пожалуйста. Если уже готовы с ответом, сверимся:

Лог в Редакторе: True, True, True, False, True, True, True, True

Лог Standalone‑сборки: True, True, True, True, True, True, True, True.

Лог Редактора:

Почему этот код возвращает True и в Редакторе, и в Standalone:

//True for Editor, True for Standalone
(object)notExistingMonobehaviour == null 

А этот — False в Редакторе и True в Standalone:

//False for Editor, True for Standalone
(object)notExistingRenderer == null 

Дело в том, что результат вызова GetComponent<Renderer>() интересен: возвращается не обычный null, а «псевдо‑null» объект. У него переопределён оператор == для проверок на null. Unity Editor выделяет такой объект для компонентов Unity вроде Renderer, MeshFilter, HingeJoint и т. д.

Однако в Standalone такой «псевдо‑null» объект не выделяется.

В то же время GetComponent<MyClassB>() возвращает настоящий null и в Редакторе, и в Standalone‑сборке.

А что насчёт TryGetComponent? Этот метод ведёт себя иначе: он всегда присваивает своему out‑параметру реальный null и не создаёт «псевдо‑null». Поэтому, выбирая между GetComponent и TryGetComponent, учитывайте различия в возвращаемых результатах.