"Fake Null"
개발을 하다보면 Destroy()로 오브젝트를 지웠는데도,
변수는 “null이 아닌 것처럼” 보이거나 ?.를 썼는데도 MissingReferenceException이 터지는 순간이 있다.
특히 Unity에서는 C# 객체(관리 객체)와 엔진 내부 네이티브 객체의 생명주기가 다르기 때문에,
겉보기엔 null인데 실제 참조는 남아있는 Fake Null 상태가 생길 수 있다.
따라서, 오늘은 Fake Null이란 무엇이고, 왜 발생하는지 간단하게 정리해 볼 예정이다.
----------------------------------------------------------------------------------------------------------------------------------------------------------------
1) Fake Null이란?
Unity에서 GameObject, Component, ScriptableObject처럼 UnityEngine.Object를 상속받는 타입은
실제로는 C# 참조가 남아있더라도 엔진 내부 네이티브 객체가 파괴되면
obj == null 비교에서 null처럼 동작할 수 있다.
즉, C# 관점의 진짜 null과
Unity 생명주기 관점의 null이 달라서 생기는 현상이다.
----------------------------------------------------------------------------------------------------------------------------------------------------------------
2) 왜 생기나? (관리 객체 vs 네이티브 객체 생명주기)

Unity 문서에서도 UnityEngine.Object 인스턴스는 네이티브 counterpart와 연결되어 있고,
네이티브 쪽이 먼저 파괴되면 관리 객체는 GC 전까지 남아 detached 상태가 될 수 있다고 설명한다.
그리고 Unity는 이런 파괴된 오브젝트 참조를 잡기 위해 == 연산자(그리고 if(obj) 같은 implicit bool)를 커스텀해서,
파괴된 객체도 null처럼 비교되도록 만들어둔다.
추가로 Destroy(obj)는 즉시 사라지는 게 아니라 현재 Update 루프 이후에 실제 파괴가 일어난다.
그래서 프레임 경계를 지나면서 Fake Null 상황을 더 자주 만나게 된다.
----------------------------------------------------------------------------------------------------------------------------------------------------------------
3) 예시 코드
using UnityEngine;
using System;
public class FakeNullEx : MonoBehaviour
{
private IEnumerator Start()
{
var go = new GameObject("Demo");
Debug.Log($"[생성 직후] go == null : {go == null}");
Debug.Log($"[생성 직후] ReferenceEquals(go, null) : {ReferenceEquals(go, null)}");
Destroy(go);
// Destroy는 프레임 끝에서 처리되므로 다음 프레임까지 기다림
yield return null;
Debug.Log($"[Destroy 후] go == null : {go == null}"); // 보통 true
Debug.Log($"[Destroy 후] ReferenceEquals(go, null) : {ReferenceEquals(go, null)}"); // false (진짜 null 아님)
Debug.Log($"[Destroy 후] (object)go == null : {(object)go == null}"); // false
}
}
go == null → true
Unity 기준으로 이미 Destroy된 오브젝트
ReferenceEquals(go, null) → false
C# 참조 자체는 아직 null이 아님(래퍼가 남아있음)
(object)go == null → false
(object)로 캐스팅하면 UnityEngine.Object의 오버로드된 ==가 아니라
System.Object 레벨의 참조 비교로 가게 되는 경우가 많다.
(오버로드 우회 목적)
결론적으로 ReferenceEquals와 같은 의미로 동작해서 false가 나오게 된다.
----------------------------------------------------------------------------------------------------------------------------------------------------------------
5) Unity에서 자주 발생하는 요인 3가지
코루틴/async에서 대기 후 접근
yield return ... / await ... 사이에 다른 로직이 오브젝트를 Destroy하면,
이후 접근 시 예외가 터질 수 있다.
?./??로 '안전 접근'했다고 믿는 경우
Unity Object에는 기대한 “안전 장치”가 아닐 수 있다.
(생명주기 체크 우회)
이벤트/콜백 구독 해제 누락
파괴된 객체가 계속 콜백을 받으면,
콜백 내부에서 참조 접근으로 예외/오동작이 발생할 수 있다.
----------------------------------------------------------------------------------------------------------------------------------------------------------------
6) 오늘의 한 줄 요약
『 Fake Null은 Destroy로 네이티브(엔진) 객체가 파괴됐지만 C# 참조는 남아 obj == null이 true처럼 동작하는 상태이다. 』
'1일 1 cs' 카테고리의 다른 글
| 17. 깊이 우선 탐색(DFS)과 너비 우선 탐색(BFS)이란? (0) | 2026.02.01 |
|---|---|
| 16. 그래프(Graph)와 트리(Tree)란? (0) | 2026.01.31 |
| 14. 정렬 알고리즘이란? (0) | 2026.01.29 |
| 13. 컬렉션(Collection)이란? (0) | 2026.01.28 |
| 12. 시간복잡도란? (0) | 2026.01.27 |
