유니티에서 ??, ?. 를 쓰면 안되는 이유

이지헌·2023년 6월 23일

Unity / C#

목록 보기
1/1

최근에 유니티 IDE를 Rider로 변경했는데, 이전에 짰던 코드 중 ??, ?. 를 사용하는 부분에서 린트 오류를 대거 뱉어내는 걸 발견했다. 린트 내용은 다음과 같았다.

'?.' on a type deriving from 'UnityEngine.Object' bypasses the lifetime check on the underlying Unity engine object

해석하자면 'UnityEngine.Object' 에서 파생된 타입에 '?.' 를 사용하면 기본 Unity 엔진 오브젝트의 생명주기 체크를 우회합니다. 라고 한다. 이게 대체 무슨 소릴까?

우리가 흔히 쓰는 MonoBeheviour 객체는 Behaviour > Component > Object 를 상속하고 있다. UnityEngine.Object 코드를 살짝 흝어보면 ==, != 등의 비교 연산자들이 오버로딩되어 있는걸 볼 수 있다. 내용은 이렇다.

public static bool operator ==(Object x, Object y) => Object.CompareBaseObjects(x, y);

public static bool operator !=(Object x, Object y) => !Object.CompareBaseObjects(x, y);

Object.CompareBaseObjects 는 다음과 같이 구현되어있다.

private static bool CompareBaseObjects(Object lhs, Object rhs)
{
    bool flag1 = (object) lhs == null;
    bool flag2 = (object) rhs == null;
    if (flag2 & flag1)
        return true;
    if (flag2)
        return !Object.IsNativeObjectAlive(lhs);
    return flag1 ? !Object.IsNativeObjectAlive(rhs) : lhs.m_InstanceID == rhs.m_InstanceID;
}

기본적인 비교 외에 operand 중 하나가 null일 경우, Object.IsNativeObjectAlive 라는 메서드를 추가로 호출하는데 이 부분이 위의 린트 에러에서 말하고 있는 'lifetime check on the underlying Unity engine object' 이다.

유니티 오브젝트는 씬이 교체되거나, Destroy() 메서드를 콜 했을 때 사라진다. 하지만 이건 유니티 엔진 상에서 destroy된 것 뿐이지 가비지 컬렉팅 되기 전 까지는 C# 객체가 null이 된 것이 아니다. 이 상태를 흔히 "fake null" 상태라고 부른다.

이같은 C# 객체 생명주기와 유니티 엔진 오브젝트 생명주기가 달라서 오는 괴리감을 해소하기 위해 비교 연산자가 오버로딩 돼있는 것이다. 실제로 null이 아니더라도, 유니티 엔진 상에서 destroy 됐다면 null 처럼 처리해주는 것이다.

문제는 ??, ?., ?[] 같은 null-conditional operator, null-coalescing operator 를 사용하면 위의 체크 과정을 생략한다.

Destroy(object);
Collider col;

if (object != null) {
	col = object.GetComponent<Collider>();
}

이 코드는 object != nullfalse를 반환해 if문 안에 들어가지 않지만,

Destroy(object);
Collider col = object?.GetComponent<Collider>();

이 코드는 object C# 객체가 null이 아니기 때문에 destroy된 오브젝트에 GetComponent()를 호출하게 되고, MissingReferenceException을 던지게 된다.

이런 모호함 때문에 null 연산자는 지양하는 편이 깔끔하고 좋을 것 같다.

+)
간혹 유니티 오브젝트의 destroy 여부와 관계 없이, 실제 C# 객체가 null인지 확인하고 싶다면, object.ReferenceEquals(object, null) 를 활용하면 된다. Unity 오브젝트의 == 는 오버로딩된 생명주기 체크 때문에 비싼 반면에, 이 메서드는 컴파일러 단에서 object == null 로 옵티마이징되기 때문에 훨씬 저렴하다.

1개의 댓글

comment-user-thumbnail
2023년 7월 2일

https://melonplaymods.com/2023/06/11/bosses-from-the-game-terraria-mod-for-melon-playground/
https://melonplaymods.com/2023/06/11/reptiloidnpc-mod-for-melon-playground/
https://melonplaymods.com/2023/06/10/entbrat-and-flowah-mod-for-melon-playground/
https://melonplaymods.com/2023/06/10/justice-league-mod-for-melon-playground/
https://melonplaymods.com/2023/06/11/portal-gun-mod-for-melon-playground/
https://melonplaymods.com/2023/06/10/jeep-mod-for-melon-playground/
https://melonplaymods.com/2023/06/10/radioactive-gun-mod-for-melon-playground/
https://melonplaymods.com/2023/06/11/masquerade-knight-w-mod-for-melon-playground/
https://melonplaymods.com/2023/06/11/airliner-mod-for-melon-playground/
https://melonplaymods.com/2023/06/11/flood-dragon-mod-for-melon-playground/
https://melonplaymods.com/2023/06/11/squid-game-characters-mod-for-melon-playground-2/
https://melonplaymods.com/2023/06/11/general-1dac-mod-for-melon-playground/
https://melonplaymods.com/2023/06/10/satellite-tower-mod-for-melon-playground/
https://melonplaymods.com/2023/06/11/characters-from-dota-2-mod-for-melon-playground/
https://melonplaymods.com/2023/06/10/wither-storm-mod-for-melon-playground/
https://melonplaymods.com/2023/06/11/pack-of-cars-mod-for-melon-playground/
https://melonplaymods.com/2023/06/10/weapon-guns-pack-mod-for-melon-playground/
https://melonplaymods.com/2023/06/10/ben-10-mod-for-melon-playground/
https://melonplaymods.com/2023/06/11/money-sdenchiksuper132-mod-for-melon-playground/
https://melonplaymods.com/2023/06/11/mechanical-vehicle2-mod-for-melon-playground/

답글 달기