유니티를 사용하다보면 Null Reference Exception 매우 많이 만나게 됩니다.
이번 포스팅에서는 이 Null Reference Exception 를 줄일 수 있게 해주는 C#의 연산자들을 살펴보도록 하겠습니다.
1. "?." 연산자
?. 연산자는 ? 조건연산자와 구분이 잘 가지 않아서 헷갈릴 수 있습니다. null 과 관련된 연산자는 물음표(?) 옆에 점(.) 이 붙습니다. 이것을 잘 구분하셔야합니다.
아무튼 먼저 ?. 연산자에 대해 살펴보도록 하겠습니다.
?. 연산자를 사용하는 기본 방법은 다음과 같습니다.
(객체변수)?.(객체변수가 null이 아닐때 참조하는 프로퍼티 혹은 실행하는 함수)
주로 어떤 객체 변수가 null인지 아닌지를 판단할 때 사용합니다.
(일반적인 int 값이나 bool 값은 null 값을 가질 수 없기 때문에 주로 클래스나 객체 변수에 사용합니다.)
만일 객체 변수 값이 null 이라면 null 값을 반환하고, null이 아니라면 ?. 뒤에 오는 프로퍼티를 참조하거나 ?.뒤에 오는 함수를 실행하게 됩니다.
예제를 살펴보겠습니다.
public class Item
{
public string itemname;
}
string ItemName(Item equipmment )
{
string name = equipmment.itemname;
return name;
}
먼저 Item이라는 클래스를 생성한뒤 맴버의 변수로 itemname을 선언해줍니다.
그리고 장비하고 있는 아이템의 이름을 string값으로 반환해주는 함수를 하나 만들었습니다. 매개변수로 Item 타입의 객체인 equipment를 받아서 이름을 반환해주는 함수입니다.
그런데 만일 이때 equipment가 null 이라면 Null Reference Exception 오류가 등장하게 됩니다.
그렇기 때문에 이런 상황을 막기 위해서 다음과 같은 코드를 만들 수 있습니다.
string ItemName(Item equipmment )
{
string name;
if (equipmment != null)
{
name = equipmment.itemname;
}
else
{
name = null;
}
return name;
}
만일 equipment가 null이 아니라면 이름을 반환하고, 만일 null이라면 null값을 반환하는 코드입니다.
간단하지만 좀 지저분하고 길게 보입니다. 그러나 ?. 연산자를 활용하면 다음과 같이 줄일 수 있습니다.
string ItemName(Item equipmment )
{
string name = equipmment?.itemname;
return name;
}
간단해졌죠?
사용법은 간단합니다. equipment?. 의 의미는 만일 equipment 가 null 이라면 null을 반환하고, 그렇지 않으며 ?. 뒤에 있는 itemname이라는 프로퍼티를 반환하여 name 변수에 입력해준다는 의미입니다.
또한 ?. 연산자는 유니티이벤트함수를 호출할때도 많이 사용됩니다.
public delegate void PlayerDie();
public static event PlayerDie playerdie;
void Damage(int dmg)
{
HP -= dmg;
if(HP<0)
{
playerdie?.Invoke();
}
}
플레이어가 죽었을때 델리게이트 함수를 만들어 이벤트 처리를 하는 코드입니다. platerdie라는 이벤트가 발생했을때 PlayerDie()라는 델리게이트 함수를 실행하는 방식입니다.
playerdie?.Invoke();
데미지를 계산하는 함수에서 HP가 0보자 작으면 playerdie 라는 이벤트를 발생시킵니다.
그런데 만일 PlayerDie 델리게이트에 아무 함수도 삽입되어 있지 않다면 Null Reference Exception 오류가 발생하게 됩니다.
그것을 막기 위해서 ?. 연산자를 사용해서 만일 PlayerDie() 델리게이트에 아무 함수도 삽입 되어 있지 않다면 null 값을 반환하고, 함수가 삽입되어 있어 null값이 아니면 ?. 뒤에 있는 Invoke() 함수를 실행시키라는 의미가 되는 것입니다.
2. ?[ ] 연산자
?[ ] 연산자는 기본적으로 ?.연산자와 비슷한 기능을 하지만 ?[ ] 연산자는 피연산자가 null값이 아닐 경우 배열이나 리스트의 요소에 접근할 수 있도록 하는 연산자입니다.
?[ ] 사용법은 다음과 같습니다.
(배열 또는 리스트 변수)?[ (인데스 번호)]
?[ ] 앞에는 배열이나 리스트같은 컬렉션 변수가 오고, [ ]안에는 컬렉션이 null 값이 아니었을 경우 접근하고 싶은 요소의 인덱스 번호를 입력하여 사용하게 됩니다.
예제를 살펴보겠습니다.
List<string> oders = new List<string>();
string PrintOder(int oderNum)
{
string oder = oders?[oderNum];
return oder;
}
어떤 식당에서 주문한 메뉴 이름을 oders라는 리스트에 담아 관리한다고 해봅시다.
주문현황을 담는 string 리스트를 oders 라는 이름으로 선언합니다.
그리고 이제 주문한 메뉴이름을 string으로 반환해주는 함수를 작성했습니다.
string oder = oders?[oderNum]
만일 oders 리스트에 값이 null 이라면 null 값을 리턴해주고, 만일 null 값이 아니라면 oderNum번째 요소를 반환해줍니다.
3. ?? 연산자
다음으로 살펴볼 연산자는 ??연산자입니다. ?? 연산자는 null 병합 연산자라고도 부릅니다.
?? 연산자를 사용하는 방법은 다음과 같습니다.
(변수) ?? (변수가 null 일때 반환하는 요소)
?? 연산자는 먼저 ?? 앞에 있는 변수가 null인지 아닌지를 검사합니다. 만일 null이 아니라면 ?? 뒤의 내용은 무시되고 변수의 값이 반환됩니다. 그러나 만일 변수의 값이 null이라면 ?? 뒤에 있는 요소를 실행해서 반환합니다.
예제를 살펴보겠습니다.
Item sword;
Item gun;
string EquipedItem()
{
Item item = sword ?? gun;
return itemName.itemname;
}
Item 클래스의 객체인 sword 와 gun 이라는 객체를 선언해줍니다.
그리고 현재 플레이어가 가지고 있는 아이템의 이름을 반환해주는 EquipedItem를 만들었습니다.
먼저 Item 타입의 item이라는 변수에 sword 아니면 gun이 담기도록 할겁니다. 그런데 우선순위는 sword에게 있습니다.
Item item = sword ?? gun
만일 sword 값이 null 이라면 gun을 item변수에 입력하는 것이고, 만일 sword 값이null 이 아니라면 sword 값을 item에 입력해 리턴해줍니다.
?? 앞에 요소가 null 아니면 그대로 사용하고 null 이면 ?? 뒤의 값을 사용하는 의미로 받아드리시면 될 것 같습니다.
4. ??= 연산자
??= 연산자는 null병합할당자라고도 부르는 연산자 입니다.
??= 연산자의 사용방법은 다음고 같습니다.
(변수) ??= (변수가 null 일때만 할당하는 값)
어떤 변수가 null 일때만 ??= 뒤에 있는 값을 할당해 줍니다. 그러나 만일 null이 아닌 경우에는 그냥 원래 할당 되어 있는 값을 유지합니다.
예제를 살펴보겠습니다.
string ItemName(string itemname)
{
itemname ??= "아무것도 착용하고 있지 않습니다.";
return itemname;
}
아이템 이름을 표시해주는 UI를 만들고 그 UI에 표시될 택스트를 반환해주는 ItemName 함수를 만들었습니다.
itemname ??= "아무것도 착용하고 있지 않습니다."
만일 itemname의 값이 null이라면 itemname에는 "아무것도 착용하고 있지 않습니다."라는 값이 입력됩니다. 그러나 만약에 itemname의 값에 다른 값이 할당되어 있다면 "아무것도 착용하고 있지 않습니다." 라는 값이 아닌 원래 할당되 있는 값이 리턴 되게 됩니다.
5. ?. 연산자와 ?? 연산자 사용의 응용
?. 연산자와 ?? 연산자를 함께 사용하면 좀 더 효율적인 코드를 완성할 수 있습니다.
예제를 살펴보겠습니다.
int Damage(int pow, Item amor)
{
int damage = pow - amor.defence;
return damage;
}
플레이어가 받는 데미지를 계산하여 int 값으로 반환하는 함수를 만들었습니다. 공격들어 온 pow에서 장비하고 있는 아머의 defence 값을 빼서 계산하려고 합니다.
그런데 만일 장비하고 있는 amor가 null이라면 Null Reference Exception 오류가 발생하게 됩니다.
이를 방지하기 위해서 다음과 같이 코드를 변환할 수 있습니다.
int Damage(int pow, Item amor)
{
int damage;
if(amor != null)
{
damage = pow - amor.defence;
}
else
{
damage = pow;
}
return damage;
}
하지만 ?. 연산자와 ?? 연산자를 동시에 사용해 응용하면 다음과 같은 코드로 같은 기능을 구현할 수 있습니다.
int Damage(int pow, Item amor)
{
int damage = pow - amor?.defence ?? 0;
return damage;
}
int damage = pow - amor?.defence ?? 0
amor?.defence : 만일 amor값이 null이 아니라면 amor.defence 값을 반환해줍니다.
그런데 만일 amor 값이 null이라면 ?. 연산자에 의해 null 값을 반환해줍니다.
?? 0 : ?? 연산자 앞에 있는 amor?.defence 가 null 값을 가질때만 ?? 연산자 뒤에 있는 0이 반환됩니다.
따라서 amor가 null 이 아니면 amor.defence값을 amor가 null 이라면 0 값을 pow에서 뺄 수 있게 되는 겁니다.
그런데 만일 이런식으로 코드를 짜면 더 쉽다고 생각하시는 분들이 계실지 모르겠습니다.
int Damage(int pow, Item amor)
{
int damage = pow - amor?.defence;
return damage;
}
int damage = pow - amor?.defence
그런데 이렇게 해보시면 아마 오류가 나온다는 것을 확인하실 수 있을 겁니다. 왜냐하면 int 타입변수는 null 값을 할당할 수 없는 변수입니다. amor?.defence 값이 null 이 반환될 확률이 존재하는 순간 int 값에는 사용할 수 없게 됩니다.
만일 이 코드를 억지로 오류를 고치려고 한다면
int? Damage(int pow, Item amor)
{
int? damage = pow - amor?.defence;
return damage;
}
이런 식으로 할 수 있습니다.
int? 데이터 타입은 int 변수에 null까지 할당 할 수 있게 해주는 데이터 타입입니다.
그러나 오류가 사라진다고 해도 만일 amor?.defence 값이 null 일때 pow - 0 이 되는 것이 아니라 pow- null 이 되어서 결국 damage로 반환되는 값은 pow 값이 아닌 null 값이 됩니다.
그렇기 떄문에 ?. 만 사용하는 것이 아니라 ?. 와 ??를 함께 사용해서 이를 효율적인 코드로 완성할 수 있는 것입니다.
이번 포스팅은 Null Reference Exception 오류를 줄일 수 있는 null 연산자들에 대해서 살펴봤습니다.
감사합니다.
'유니티 기초 스킬' 카테고리의 다른 글
| ? 조건연산자(삼항연산자) (2) | 2024.05.01 |
|---|---|
| 중복없는 랜덤 숫자 추출하기 (0) | 2024.04.25 |
| TextMeshPro(텍스트 메시 프로) 한글폰트 추가 방법 (0) | 2024.04.24 |