C# 기초부터 마스터까지 — 6화
컬렉션과 LINQ로 데이터 다루기
본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.
게임과 앱은 많은 데이터를 저장하고 검색하고 정렬한다. 컬렉션을 목적에 맞게 고르고 LINQ로 처리 의도를 표현하면 코드가 짧아지는 것보다 흐름이 선명해진다.
컬렉션 선택
var party = new List<Player>();
var itemById = new Dictionary<int, Item>();
var unlockedIds = new HashSet<int>();
itemById.Add(10, new Item(10, "Potion"));
unlockedIds.Add(10);
List는 순서 있는 목록, Dictionary는 키 검색, HashSet은 값의 존재 여부와 중복 방지에 어울린다. Dictionary에서 없는 키를 인덱서로 읽으면 예외가 발생하므로 TryGetValue를 사용할 수 있다.
LINQ로 질의하기
List<Monster> targets = monsters
.Where(monster => monster.IsAlive)
.OrderBy(monster => monster.Health)
.Take(3)
.ToList();
살아 있는 몬스터 중 체력이 낮은 세 마리를 고른다는 의도가 연산 순서에 드러난다. LINQ는 원본 컬렉션을 자동으로 변경하지 않고 새로운 결과 흐름을 만든다.
지연 실행
IEnumerable<Monster> alive = monsters.Where(monster => monster.IsAlive);
monsters.Add(new Monster());
foreach (Monster monster in alive)
{
Console.WriteLine(monster.Health);
}
Where가 반환한 질의는 보통 순회하는 시점에 실행된다. 중간에 원본이 바뀌면 결과도 달라질 수 있으며, 현재 결과를 고정하려면 ToList()로 즉시 계산한다.
반복 비용 확인하기
LINQ 호출이 무조건 느린 것은 아니지만 반복자, 델리게이트, 결과 컬렉션이 만들어질 수 있다. 매 프레임 수천 번 실행되는 경로라면 단순 반복문과 버퍼 재사용이 더 적합할 수 있다.
targets.Clear();
foreach (Monster monster in monsters)
{
if (monster.IsAlive)
{
targets.Add(monster);
}
}
먼저 읽기 쉬운 질의를 만들고 실제 측정에서 문제가 확인된 구간만 바꾸는 것이 좋다. 컬렉션과 LINQ의 핵심은 가장 짧은 문법이 아니라 데이터의 저장 목적과 처리 시점을 분명하게 만드는 것이다.