본문 바로가기

C#/게임 제작 + TIL

C# 컬렉션 (FirstOrDefault(); )

콘솔로 텍스트 RPG를 만들며 이것저것 테스트를 해보던 중 인벤토리에 존재하는, List 형식으로 구현된 Item에 접근하여 데이터를 확인할 때, 사용한 방법 및 문제 발생과 해결을 써보려고 한다.

 

 

아래는 현재 텍스트 RPG에서 사용하는 구조체 형식의 Item 이다.

 public struct Item
 {
     public Item(string? name, int typenum, int value, string disc, int gold)
     {
         Name = name;
         Type = (ItemType)typenum;
         Value = value;
         Description = disc;
         Gold = gold;
     }
 }

 

이러한 구조체 형식은 일반적인 class와는 다르게 값 형식으로, 스택 메모리에 저장되는 만큼 타 객체로부터 접근이 까다롭고, 생성자를 필수로 요구한다는 특징을 가진다.

 

 

그리고 다음은 이번 글의 주제인 FirstOrDefault(); 이다.

static string CheckBought(Player player, Item? itemDT)
{
    Item? item = player.Inven.FirstOrDefault(x => x.Name == itemDT?.Name);//만약 플레이어의 인벤에 있는 아이템에 접근시...
    if (item? != null)
    {
        //플레이어의 인벤에 동일한 아이템 존재
        return $"구매완료";
    }
    else
    {
        //플레이어의 인벤에 없는 아이템
        return $"{itemDT?.Gold} G";
    }

}

 

해당 코드는 아이템을 인벤토리에서 표시해 주는 단계에서 아이템의 보유 유무를 확인하는 메서드인데, 처음 해당 기능을 사용하려 했을 때 if문에 FirstOrDefault를 통해서 받은 Item 타입의 값의 null여부로, 동일 아이템의 존재를 체크했었다.

 

하지만 전혀 원하는 대로 작동하지 않았고 이는 FirstOrDefault와 구조체의 특징을 제대로 파악하지 못해서 일어난 일이었다.

 

 

 

먼저 FirstOrDefault의 기능은, 컬렉션에서

 

1. 만약 매개변수가 생략된 기본형식의 경우, 컬렉션의 첫 번째 요소를.

2. 매개변수 또는 람다식으로 지정된 조건을 체킹 할 경우, 해당 조건을 만족하는 첫 번째 요소를.

3. 위의 두 방식 중 조건을 만족하는 요소가 없거나, 컬렉션 자체가 비어있을 경우, 데이터 유형의 기본값을.

 

각각 반환하는 형태이다.

여기서 1,2번은 직관적으로 알아볼 수 있는 구조에서의 기능으로 바로 이해가 가능하지만 3번의 데이터 유형의 기본값 의경우엔 다르다.

 

여기서 말하는 데이터 유형의 기본값은 

https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/builtin-types/default-values

 

기본 제공 형식의 기본값 - C# reference

bool, char, int, float, double 등과 같은 C# 형식의 기본값을 알아봅니다.

learn.microsoft.com

마이크로 소프트 공식 홈페이지에서도 볼 수 있듯 각각 다르며, 여기서 우리가 사용하고자 했던 구조체의 기본값도 설명하고 있다.

 

" 모든 값 형식 필드를 기본값으로 설정하고 모든 참조 형식 필드를 null으로 설정하여 생성된 값. "

 

으로 일반적으로 참조형 형식의 class의 경우, default 값은 null이 되지만, 구조체는 그 자체가 null이 되는 것이 아니라 

구조체의 생성자를 통해서 정해지는 각 요소(모든 값/참조 형식 필드)들을 기본값으로 설정한다.

 

따라서 저기서 if(item? != null) 로 확인을 해버리면, 값 형식인 구조체가 null이 되는 경우는 없으니 무조건 if문에서 걸리게 돼버리는 것이다.

 

 

 

 

결국 default값의 이해와 구조체 자체의 특성에 대한 이해가 부족해서 발생한 문제를 아래와 같이 수정하였다.

static string CheckBought(Player player, Item? itemDT)
{
    Item? item = player.Inven.FirstOrDefault(x => x.Name == itemDT?.Name);//만약 플레이어의 인벤에 있는 아이템에 접근시...
    if (item?.Name != null)
    {
        //플레이어의 인벤에 동일한 아이템 존재
        return $"구매완료";
    }
    else
    {
        //플레이어의 인벤에 없는 아이템
        return $"{itemDT?.Gold} G";
    }

}

 

 

이렇게 수정하면 item.name은 string 형식으로 기본값이 null로 설정되어, 만약 플레이어의 Inven에 동일한 아이템이 없을 경우 item.Name 은 null이 되어 else문까지 넘어가게 되는 것이다.