Намедни столкнулся с неожиданным (по крайней мере для меня) исключением на банальном куске кода в Unity. Если убрать все лишнее, то получится примерно вот такой скрипт:
1 2 3 4 5 6 7 8 |
public class TestScript : MonoBehaviour { private void Start() { var cg = GetComponent<CanvasGroup>() ?? gameObject.AddComponent<CanvasGroup>(); cg.alpha = .5f; } } |
Вышепреведенный код отрабатывал без ошибок, если на игровом объекте висел компонент CanvasGroup . А если этого компонента нет, то код генерирует исключение:
MissingComponentException: There is no ‘CanvasGroup’ attached to the «Button» game object, but a script is trying to access it.
You probably need to add a CanvasGroup to the game object «Button». Or your script needs to check if the component is attached before using it.
TestScript.Start () (at Assets/TestScript.cs:10)
По задумке, само собой, не ожидалось никакого исключения. Какое-то время потребовалось, чтобы понять, почему генерируется это исключение. Я, конечно, был в курсе, что Unity переопределяет оператор сравнения и что использовать операторы ?. и ?? с Unity.Object и его наследников не следует. Но почему-то мне казалось, что переопределенный оператор на что-то влияет, только если мы имее дело с уничтоженным объектом. В моем же коде, по идее, этого не должно было быть. И потому это исключение оказалось неожиданным. Логично было бы предположить, что вместо MissingComponentException будет генерироваться исключение NullPointerException . Но, как вы уже поняли, этого не происходит. Согласно документации, метод GetComponent :
Returns the component of Type
type
if the game object has one attached, null if it doesn’t.
Этот метод должен вернуть ссылку на компонент или null , если компонент не найден. А далее приводится пример:
1 2 3 4 5 6 7 8 9 10 11 12 |
using UnityEngine; public class GetComponentExample : MonoBehaviour { void Start() { HingeJoint hinge = gameObject.GetComponent(typeof(HingeJoint)) as HingeJoint; if (hinge != null) hinge.useSpring = false; } } |
который «подтверждает» ранее описанное поведение. Но если бы это была бы правда, то мой код бы не генерировал исключение. А он генерирует. Дело в том, что метод GetComponent возвращает ссылку на управляемый компонент, даже если компонент не найден. Иными словами, если запрашиваемый компонент не найден, то все равно возвращается ссылка на управляемый компонент, который не ссылается на настоящий C++ компонент и переопределенный оператор сравнения с null не срабатывает. Создается ощущение, что вернулся null . Это легко подтвердить, например, таким кодом:
1 |
Debug.Log(GetComponent<CanvasGroup>().GetType()); |
В результате выполнения этого кода мы увидим:
UnityEngine.CanvasGroup
UnityEngine.Debug:Log(Object)
TestScript:Start() (at Assets/TestScript.cs:9)
Также стоит отметить, что, если сбилдить приложение, то тестовый код выполнится без ошибок, а ненайденный компонент будет добавлен. Происходит это потому, что в run-time GetComponent будет возвращаться настоящий null , и все выполнится, как и задумывалось. Т.е. подмена возвращаемого значения работает только в редакторе Unity. Само собой, не стоит использовать операторы ?. и ?? с Unity.Object и его наследниками в реальном коде.