TGViewer
Unity Architect: архитектура unity проектов Unity Architect: архитектура unity проектов @uniarchitect · 5.23K subscribers
Post #65 3.38K
UnityEngine.Object == null

Мне не так давно написал один из разработчиков на проекте, который столкнулся с проблемой зомби объектов.

Зомби объект — жив потому что GC не может его собрать, так как в стэке есть ссылка на него, но сам объект уже Destroyed.

И дело тут не в том, что GC.Collect еще не вызвался, а в том что мы храним указатель на объект, который уже уничтожен.

Такую ситуацию очень легко получить:
🔹Берем любой класс наследник MonoBehaviour
🔹 Реализуем в нем любой интерфейс
🔹 Instantiate'им объект, сохраняем в переменную
🔹 Во вторую переменную cast'им наш объект к интерфейсу
🔹 Уничтожаем MonoBehaviour
Через Object.Destroy или Object.DestroyImmediate
🔹 Пытаемся вызвать поле или метод интерфейса

Вопросы:
🔸 Будет ли MonoBehaviour == null?
🔸 Будет ли переменная интерфейса == null?
🔸 Будет ли ошибка при вызове любого member'а интерфейса?

Подсказка

Ответы:
👍 Да
👎 Нет
❓ И да и нет.
Если вызваемый мембер не часть MonoBehavior имплементации - ошибки не будет
В остальных случаях будет MissingReferenceException


Почему так?
Потому что любой UnityEngine.Object имеет 2 runtime части:
🔹Одна на стороне unity (написанная на C++)
🔹Вторая на стороне CLR (Common Language Runtime) — класс/структура C#

Это значит:
🔸 CLRObject может смотреть уже на уничтоженный UnityObject ровно столько, сколько мы храним указатель на него
🔸 Если UnityObject уничтожен, мы все еще можем обратиться к его CLR части и взять оттуда любые данные, которые не является частью UnityObject
🔸 Момент сборки данного объекта GC может быть отложен на сколько угодно по времени

Это ведет к проблемам:
♦️ Утечки памяти по всему проекту.
UnityObject уничтожен и нам нужно удалить объект из коллекции, а мы не можем, потому что interface != null
♦️MissingReferenceException, в неожиданных местах со вкусом фрустрации и сложной отладки

4 возможных решения:
1️⃣ Проверять все экземпляры типа интерфейса методом:
bool IsNullUniversal<T>(T instance)
{
if (instance is UnityEngine.Object unityObject)
return unityObject == null;

return instance == null;
}

2️⃣ Для абстракции ВСЕХ монобехов использовать только abstract классы
3️⃣ Наследовать все интерфейсы от IEquatable<T> и использовать везде метод Equals вместо ==
4️⃣ (самый быстрый, самый дерзкий)
Через UnsafeUtility читать m_CachedPtr и m_InstanceID и через рефлексию дергать метод DoesObjectWithInstanceIDExist
Вот как это примерно может выглядеть

Почему именно так:
🔸UnityObject содержит перегрузку оператора == и != которая проверят что нативная (C++ часть) "жива"
🔻Но в CLR части == транслируется в операцию seq, которая в после IL2CPP будет выглядеть так:
((((RuntimeObject*)instance) == ((RuntimeObject*)NULL))? 1 : 0)


Или проще говоря в обычное сравнение 2ух указателей.

Такой проверки в случае реализации интерфейса в UnityObject не достаточно.
Потому мы получаем false-negative результат при сравнение на null, который потенциально ведет к утечкам памяти

Я создал Gist в котором расписал подробно TestCase'ы и решение.
Не стесняйтесь его дополнить или предложить свой вариант в комментариях 😎

#будни@UniArchitect
  • 👍 33
  • 🔥 11
  • 🤯 4
More from @uniarchitect
  1. Jul 24, 2026Post #199
  2. Jul 12, 2026AI НЕ ДЕЛАЕТ ВАС ПРОДУКТИВНЕЕ Незыблемый факт: AI уже очень хорош в маленьких задачах. Нап…
  3. Jul 9, 2026Post #196
  4. Jul 7, 2026UNITY BUILD PIPELINE ПО КИРПИЧИКАМ Полгода назад я решил попробовать активность в блоге, г…
  5. Jul 4, 2026ПРОКЛЯТИЕ ПЕРЕИСПОЛЬЗУЕМОСТИ Делюсь болью. Я последние 5 лет на разных уровнях у разработч…
  6. Jun 29, 2026AI КАК РЕДАКТОР, А НЕ АВТОР Это вторая статья из серии про AI. Первая тут. Когда я запуска…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →