최근에 유튜브에서 관련 내용을 봐서 한번 정리해봤습니다.
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 |