LINQ ( Language Integrated Query )
데이터베이스를 위한 프로그래밍에서는 자료의 탐색 , 필터링 , 정렬 등의 기능을 많이 사용한다.
데이터베이스에 있는 자료를 찾고 , 걸러내고 , 필요하다면 순서대로 나열하는 것이 데이터베이스에서 필요한 데이터를 뽑아내는 방법이기 때문이다. 자료에 대한 취급을 더 쉽게 하기 위해 데이터베이스에서는 LINQ 기능을 사용한다.
LINQ 는 컬렉션에 들어있는 데이터를 더 편하게 검색 , 필터링 , 정렬 , 변환할 수 있도록 도와주는 C# 기능이다.
배열이나 리스트에 데이터가 많아질수록 원하는 값을 직접 찾기 위해 반복문과 조건문을 계속 작성해야 한다.
이때 LINQ 를 사용하면 같은 작업을 더 짧고 읽기 쉬운 코드로 표현할 수 있다.
using System.Collections.Generic;
public class LinqExample
{
private List<int> numbers = new List<int>() { 25, 13, 16, 16, 42 };
private int CountNumber(int target)
{
int count = 0;
foreach (int number in numbers)
{
if (number == target)
{
count++;
}
}
return count;
}
}
▲LINQ 를 사용하지 않고 , 리스트 안에서 특정 숫자가 몇 개 있는지 찾는 코드
위 코드의 동작 자체는 단순하다.
하지만 원하는 값을 찾기 위해 직접 foreach 문을 돌리고 , 조건문으로 값을 비교한 뒤 , 개수를 증가시키는 과정이 필요하다.
데이터가 많아지거나 조건이 복잡해지면 코드의 길이가 길어지고 , 핵심 의도가 한눈에 잘 보이지 않을 수 있다.
같은 기능을 LINQ 로 작성하면 다음과 같이 표현할 수 있다.
using System.Collections.Generic;
using System.Linq;
public class LinqExample
{
private List<int> numbers = new List<int>() { 25, 13, 16, 16, 42 };
private int CountNumber(int target)
{
return numbers
.Where(number => number == target)
.Count();
}
}
Where() 는 조건에 맞는 데이터만 걸러내는 기능이다.
위 코드에서는 number == target 조건을 만족하는 숫자만 골라낸다.
그리고 Count() 를 사용해서 조건에 맞는 데이터의 개수를 반환한다.
즉 , 이 코드는 다음과 같은 의미를 가진다.
numbers 리스트에서
target과 같은 값만 골라낸 뒤
그 개수를 반환한다.
반복문을 직접 작성하지 않아도 코드의 의도가 비교적 명확하게 드러난다.
LINQ 를 사용하는 이유
LINQ 를 사용하면 컬렉션 데이터를 다룰 때 코드가 더 직관적으로 바뀐다.
예시를 들기 위해 게임에서 여러 몬스터 데이터 중에서 일반 몬스터이면서 플레이어가 가진 돈으로 구매 가능한 몬스터만 찾고 싶다고 가정한다. 먼저 몬스터 데이터를 다음과 같이 구성할 수 있다.
public enum MonsterType
{
Normal,
Boss
}
public class MonsterData
{
public string Name { get; private set; }
public MonsterType Type { get; private set; }
public int Price { get; private set; }
public MonsterData(string name, MonsterType type, int price)
{
Name = name;
Type = type;
Price = price;
}
}
그리고 플레이어가 가진 골드로 구매 가능한 일반 몬스터만 찾는 코드는 다음과 같이 작성할 수 있다.
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class PlayerShop : MonoBehaviour
{
private int gold = 100;
private List<MonsterData> monsters = new List<MonsterData>()
{
new MonsterData("Goblin", MonsterType.Normal, 30),
new MonsterData("Orc", MonsterType.Normal, 80),
new MonsterData("Dragon", MonsterType.Boss, 300)
};
private void Start()
{
MonsterData[] availableMonsters = GetAvailableMonsters();
foreach (MonsterData monster in availableMonsters)
{
Debug.Log(monster.Name);
}
}
private MonsterData[] GetAvailableMonsters()
{
return monsters
.Where(monster => monster.Type == MonsterType.Normal)
.Where(monster => monster.Price <= gold)
.ToArray();
}
}
위 코드에서 핵심은 이 부분이다.
return monsters
.Where(monster => monster.Type == MonsterType.Normal)
.Where(monster => monster.Price <= gold)
.ToArray();
monsters 리스트에서
일반 몬스터만 고르고
가격이 현재 골드 이하인 몬스터만 다시 고른 뒤
배열로 변환해서 반환한다.
LINQ 를 사용하지 않았다면 foreach 문 안에서 몬스터 타입을 검사하고 , 가격도 검사한 뒤 , 조건에 맞는 몬스터를 새로운 리스트에 추가하고 , 마지막에 배열로 변환하는 과정이 필요했을 것이다.
하지만 LINQ 를 사용하면 데이터를 어떤 조건으로 골라낼 것인지가 코드에 바로 드러난다.
자주 사용하는 LINQ 기능
처음에는 모든 기능을 외우기보다 자주 쓰는 것부터 익히는 것이 좋다.
Where() // 조건에 맞는 데이터만 필터링
Select() // 데이터를 다른 형태로 변환
OrderBy() // 오름차순 정렬
OrderByDescending() // 내림차순 정렬
FirstOrDefault() // 조건에 맞는 첫 번째 데이터 찾기
Any() // 조건을 만족하는 데이터가 하나라도 있는지 확인
Count() // 데이터 개수 구하기
ToList() // List로 변환
ToArray() // 배열로 변환
예시 코드
▼살아있는 적만 찾고 싶을때
var aliveEnemies = enemies
.Where(enemy => !enemy.IsDead)
.ToList();
▼가장 가까운 적 하나를 찾고 싶을때
var nearestEnemy = enemies
.OrderBy(enemy => Vector3.Distance(transform.position, enemy.transform.position))
.FirstOrDefault();
Unity 에서 사용할 때 주의할 점
LINQ 는 코드를 읽기 쉽게 만들어주지만 , Unity 에서는 사용할 위치를 조금 조심해야 한다.
특히 Update() 처럼 매 프레임 실행되는 함수 안에서 LINQ 를 반복적으로 사용하면 불필요한 메모리 할당이 생길 수 있다.
private void Update()
{
var aliveEnemies = enemies
.Where(enemy => !enemy.IsDead)
.ToList();
}
위 코드처럼 매 프레임 ToList() 를 호출하면 계속 새로운 리스트가 만들어질 수 있다.
그래서 LINQ 는 다음과 같은 상황에서 사용하는 것이 좋다.
- 초기 데이터 정리
- 버튼 클릭 시 목록 필터링
- 상점 UI 갱신
- 웨이브 데이터 검색
- 디버그용 데이터 확인
반대로 매 프레임 빠르게 반복되는 전투 로직이다 충돌 판정에서는 일반 반복문이 더 적합할 수 있다.
정리
LINQ 는 자료구조가 아니라 , 자료구조 안의 데이터를 편하게 다루기 위한 기능이다.
배열 , 리스트 , 딕셔너리 같은 컬렉션에서 원하는 데이터를 찾거나 , 조건에 맞게 걸러내거나 , 정렬하고 변환할 때 사용할 수 있다.
- 자료구조 : 데이터를 담는 방식
- LINQ : 담긴 데이터를 조회하고 가공하는 기능
LINQ 를 사용하면 반복문과 조건문으로 길게 작성해야 하는 코드를 더 간결하고 직관적으로 표현할 수 있다.
다만 Unity 에서는 성능이 중요한 반복 구간에서 무분별하게 사용하기보다는 , 가독성이 중요한 데이터 검색이나 UI 갱신 로직에서 적절히 사용하는 것이 좋다.
참고자료
System.Linq : https://learn.microsoft.com/ko-kr/dotnet/api/system.linq?view=net-7.0