Null 관련 연산자

최근에 유튜브에서 관련 내용을 봐서 한번 정리해봤습니다.

C#에서의 연산자와 유니티에서의 사용을 나눠서 작성했습니다.

 

 

1. Nullable (?)

값타입 변수에 null을 허용하는 선언

일반적으로 int, float같은 값타입에는 0을 넣을 수는 있어도 null을 넣을 수 없다.

하지만 int?로 변수를 선언하면 null을 넣을 수 있게된다.

 

1.1. 기본 사용법

int? nullableInt = null;
float? nullableFloat = null;
bool? nullableBool = null;

// 일반적인 값도 할당 가능
nullableInt = 10;

 

1.2. 형변환 규칙

Nullable은 null값을 넣을 수 있기때문에 일반 값타입보다 범위가 넓다.

그래서 일반 타입에서 Nullable타입으로는 암시적 변환이 가능하지만, 반대는 명시적으로 변환하는 과정이 필요하다.

// 일반 타입 → Nullable 타입 (암시적 변환 가능)
int normalInt = 10;
int? nullableInt = normalInt;  // 가능

// Nullable 타입 → 일반 타입 (명시적 변환 필요)
int? nullableValue = 20;
int normalValue1 = (int)nullableValue;  // 가능하지만 null일 경우 예외 발생
int normalValue2 = nullableValue.Value;  // 가능하지만 null일 경우 예외 발생

 

1.3. GetValueOrDefault 

null일경우의 에러를 막기 위해 GetValueOrDefault() 함수를 쓸 수 있다.

이때 매개변수를 넣으면 null일때 해당 값이 반환된다.(default 0)

int? nullNum = null;

// 기본값(0) 반환
int result1 = nullNum.GetValueOrDefault();  // 결과: 0

// 지정된 기본값(100) 반환
int result2 = nullNum.GetValueOrDefault(100);  // 결과: 100

// null이 아닐 경우 해당 값 반환
nullNum = 50;
int result3 = nullNum.GetValueOrDefault(100);  // 결과: 50

 

 

2. Null 조건 연산자 (?.)

앞의 값이 null일때 뒤를 실행하지 않는다.

연산자의 위치에서 끊기 때문에 원래는 Nullreference 에러가 나는 상황에서도 null을 넣을 수 있게된다.

참조 타입에서 사용된다.

ex)

class Player
{
    string name;
    int score;
}

Player player = null;
string playerName = player?.name;  // null
int? playerScore = player?.score;  // null

// 체이닝 가능
string upperName = player?.name?.ToUpper();  // null

 

2.1. Action

public event Action onStarted;

void Start()
{
    onStarted?.Invoke(); 
}

 

 

3. Null 병합 연산자 (??)

?.와 반대로 앞이 null일경우 뒤를 실행한다.

 

ex)

int? nullValue = null;
int result1 = nullValue ?? 10;  // 결과: 10

string nullString = null;
string result2 = nullString ?? "기본값";  // 결과: "기본값"

List<int> result3;
result3 ??= new List<int>(); // 리스트가 없으면 생성

// 체이닝
string result = nullString1 ?? nullString2 ?? "모두 null일 때 사용할 값";

 

 

4. Null 조건부 인덱스 연산자 (?[])

참조형식에서 앞이 null이면 뒤를 실행하지 않는다.

 

ex)

// 배열에서 사용
int[] numbers = null;
int? firstNumber = numbers?[0];  // null

// 배열이 존재할 경우
numbers = new int[] { 1, 2, 3 };
firstNumber = numbers?[0];  // 1

// 리스트에서 사용
List<string> items = null;
string firstItem = items?[0] ?? "비어있음";  // "비어있음"

 

 

 

5. Unity에서의 사용

위의 내용은 C#의 문법이기 때문에 당연히 유니티 스크립트에서도 사용할 수 있다.

하지만 유니티에서는 해당 연산자를 따로 정의해놓지 않았다.

그래서 유니티 고유의 객체에서 해당 연산자를 사용하면 예상하지 못한 에러가 날 수 있다.

 

5.1. Unity에서의 null

유니티에서도 int, float같은 시스템 오브젝트에는 사용할 수 있지만, UnityEngine.Object를 상속받는 객체들(GameObject 등)은 일반적인 C#객체와는 다른 특별한 null 처리를 한다.

그렇기 때문에 유니티에서 정의해놓은 문법이 아닌 Null관련 연산자들을 사용할 경우 'Fake null'에 의한 문제가 발생한다.

 

ex)

public class Test : MonoBehaviour
{
    public GameObject object1;
    public GameObject object2;

    void Example()
    {
        // Unity Object가 파괴된 경우
        Destroy(object1);

        // C#의 관점에서는 object1 != null 
        // Unity의 관점에서는 object1 == null (fake null)
        
        // 잘못된 null 체크 방식
        GameObject result = object1 ?? object2;
    }
}

 

5.2. 일반적인 사용

null 조건 연산자의 null이면 실행하지 않는 특성때문에 Action 변수와 같이 사용된다.

Action은 순수 C#의 delegate이기 때문에 fake null 문제가 발생하지 않는다.

public class Test : MonoBehaviour
{
    public event Action<string> OnGameEvent;
    
    public void TriggerEvent(string message)
    {
        // 이벤트 핸들러가 없을 때의 null 체크
        OnGameEvent?.Invoke(message);
    }
}

 

 

 

위에서 언급했듯 Unity의 내부 null체크는 네이티브 코드를 호출하므로 일반 C#의 null체크보다 비용이 크다.

그러므로 자주 사용되는 컴포넌트는 Awake나 Start에서 캐싱하고 재사용하는것이 좋다.

특히 Update같은 자주 호출되는 메서드에서는 불필요한 null체크를 최소화해야한다.

'Unity' 카테고리의 다른 글

Delegate, Action, Event  (1) 2024.11.07
벡터  (0) 2024.11.01
비동기 프로그래밍  (0) 2024.09.08
Rigidbody.velocity 천천히 떨어지는 문제  (0) 2024.04.21