LINQ 마스터하기

순한양·2025년 6월 2일
0

⛓️ LINQ 마스터하기: 유니티 C# 데이터 처리를 프로처럼! 🚀

안녕하세요! 유니티 개발 시 수많은 데이터를 다루다 보면 "이 많은 걸 언제 다 정렬하고, 필터링하지?" 막막할 때가 있으셨죠? 최근 진행된 유니티 스탠다드반 특강에서는 바로 이런 고민을 해결해 줄 강력한 도구, LINQ(Language Integrated Query)에 대해 배우는 시간을 가졌습니다. 이 글에서 그 핵심 내용들을 다시 한번 되짚어 볼게요!

Ⅰ. LINQ란 무엇이고, 왜 필요할까요? 🤔

LINQ는 C# 코드 내에서 마치 데이터베이스에 질의(Query)하듯 다양한 데이터 소스(리스트, 배열, XML, DB 등)의 데이터를 통합된 방식으로 다룰 수 있게 해주는 기능입니다. 특히 게임 개발에서는 수많은 아이템, 캐릭터, 몬스터 등의 정보를 조건에 맞게 검색하거나 정렬하는 데 매우 유용하게 사용됩니다.

  • LINQ를 사용하면 좋은 점:
    • 복잡한 반복문과 조건문 없이 간결하고 가독성 높은 코드로 데이터를 처리할 수 있습니다.
    • 다양한 조건으로 데이터를 필터링, 정렬, 그룹화하는 작업이 쉬워집니다.
    • 원본 데이터를 변경하지 않고 결과만을 새로운 컬렉션으로 받아옵니다. (일반 List.Sort()는 원본 자체를 변경)

Ⅱ. LINQ 사용 준비: using System.Linq;

LINQ의 다양한 확장 메서드들을 사용하기 위해서는 스크립트 상단에 다음 네임스페이스를 추가해야 합니다.

using System.Linq;

Ⅲ. LINQ 작성 스타일: 메서드 구문 vs. 쿼리 구문
LINQ를 작성하는 방식에는 크게 두 가지가 있습니다.

메서드 구문 (Method Syntax): 람다식(Lambda Expression)을 활용하여 체인 형태로 메서드를 호출하는 방식입니다. (예: 데이터컬렉션.Where(조건).Select(항목).ToList();) 이번 강의에서는 주로 이 방식을 다뤘습니다.
쿼리 구문 (Query Syntax): SQL과 유사한 형태로 작성하는 방식입니다. (예: from 항목 in 데이터컬렉션 where 조건 select 항목;) SQL에 익숙한 분들에게는 더 직관적일 수 있습니다.
(어떤 방식을 사용하든 기능은 동일하며, 가독성이나 개인 선호에 따라 선택할 수 있습니다. 이번 강의에서는 메서드 구문에 집중했습니다.)

Ⅳ. 핵심 LINQ 메서드 파헤치기 🛠️
강의에서는 가상의 Weapon 클래스 리스트를 예제로 사용하여 다양한 LINQ 메서드들을 시연했습니다. Weapon 클래스는 Name(string), Type(enum), Grade(enum), AttackPower(int), RequiredLevel(int), Price(int) 등의 속성을 가지고 있다고 가정합니다.

// 예시 Weapon 클래스 및 데이터 (강의 내용 기반)
public enum WeaponType { Sword, Bow, Staff }
public enum WeaponGrade { Common, Rare, Unique, Legendary }

public class Weapon
{
    public string Name { get; set; }
    public WeaponType Type { get; set; }
    public WeaponGrade Grade { get; set; }
    public int AttackPower { get; set; }
    public int RequiredLevel { get; set; }
    public int Price { get; set; }

    public override string ToString() // 디버그 출력을 위함
    {
        return $"이름: {Name}, 종류: {Type}, 등급: {Grade}, 공격력: {AttackPower}, 요구레벨: {RequiredLevel}, 가격: {Price}";
    }
}

// 사용 예시 데이터 리스트 (실제 강의에서는 WeaponData.DB에 미리 정의된 static 리스트 사용)
List<Weapon> weaponDatabase = new List<Weapon>()
{
    new Weapon { Name = "불의 검", Type = WeaponType.Sword, Grade = WeaponGrade.Rare, AttackPower = 15, RequiredLevel = 5, Price = 3000 },
    new Weapon { Name = "얼음의 활", Type = WeaponType.Bow, Grade = WeaponGrade.Unique, AttackPower = 25, RequiredLevel = 10, Price = 5000 },
    new Weapon { Name = "드래곤 스태프", Type = WeaponType.Staff, Grade = WeaponGrade.Legendary, AttackPower = 40, RequiredLevel = 20, Price = 10000 },
    new Weapon { Name = "낡은 단검", Type = WeaponType.Sword, Grade = WeaponGrade.Common, AttackPower = 5, RequiredLevel = 1, Price = 100 },
    new Weapon { Name = "사냥꾼의 장궁", Type = WeaponType.Bow, Grade = WeaponGrade.Rare, AttackPower = 18, RequiredLevel = 7, Price = 3500 },
    new Weapon { Name = "초보 마법사의 지팡이", Type = WeaponType.Staff, Grade = WeaponGrade.Common, AttackPower = 10, RequiredLevel = 3, Price = 1200 }
};
  1. Where() - 조건에 맞는 데이터 필터링
    조건을 만족하는 요소들만 걸러냅니다.
// 예: 가격이 3000 이상인 무기들만 가져오기
List<Weapon> expensiveWeapons = weaponDatabase
                                .Where(weapon => weapon.Price >= 3000)
                                .ToList(); // 결과를 새 리스트로 만듦

foreach (Weapon w in expensiveWeapons)
{
    Debug.Log(w.ToString());
}
// 람다식 weapon => weapon.Price >= 3000 은
// weaponDatabase의 각 Weapon 객체를 weapon이라는 이름으로 받아,
// 그 weapon의 Price가 3000 이상이면 true를 반환하는 조건식입니다.
  1. OrderBy() & OrderByDescending() - 데이터 정렬
    특정 기준에 따라 데이터를 오름차순(OrderBy) 또는 내림차순(OrderByDescending)으로 정렬합니다.
// 예: 가격이 낮은 순으로 무기 정렬
List<Weapon> sortedByPriceAsc = weaponDatabase
                                .OrderBy(weapon => weapon.Price)
                                .ToList();

// 예: 공격력이 높은 순으로 무기 정렬
List<Weapon> sortedByAttackDesc = weaponDatabase
                                  .OrderByDescending(weapon => weapon.AttackPower)
                                  .ToList();

// 예: 이름 가나다순(ABC순)으로 정렬 (문자열도 가능)
List<Weapon> sortedByName = weaponDatabase
                            .OrderBy(weapon => weapon.Name)
                            .ToList();
// 한글은 가나다 순, 영어는 ABC 순으로 정렬됩니다.
// 한글과 영어가 섞여있을 경우, 일반적으로 영어가 먼저 정렬된 후 한글이 정렬됩니다.
// (OrderByDescending을 사용하면 역순)
  1. Count(), Sum(), Average() - 집계 함수
    Count(): 조건에 맞는 요소의 개수를 반환합니다. (조건 없이 사용 시 전체 개수)
    Sum(): 숫자 타입 속성의 합계를 계산합니다.
    Average(): 숫자 타입 속성의 평균을 계산합니다. (결과는 주로 double 타입)
// 예: 전체 무기 개수
int totalCount = weaponDatabase.Count();
Debug.Log(<span class="math-inline">"전체 무기 개수\: \{totalCount\}"\);
// 예\: 가격이 1000 이상인 무기 개수
int expensiveCount \= weaponDatabase\.Count\(weapon \=\> weapon\.Price \>\= 1000\);
Debug\.Log\(</span>"가격 1000 이상 무기 개수: {expensiveCount}");

// 예: 모든 무기의 총 가격
int totalPrice = weaponDatabase.Sum(weapon => weapon.Price);
Debug.Log(<span class="math-inline">"모든 무기 총 가격\: \{totalPrice\}"\);
// 예\: 모든 무기의 평균 가격
double averagePrice \= weaponDatabase\.Average\(weapon \=\> weapon\.Price\);
Debug\.Log\(</span>"모든 무기 평균 가격: {averagePrice:F1}"); // F1은 소수점 첫째 자리까지 표시
  1. GroupBy() - 특정 기준으로 데이터 그룹화
    지정한 키를 기준으로 요소들을 그룹으로 묶습니다. 결과 타입은
IGrouping<TKey, TElement>의 컬렉션입니다.
// 예: 무기 등급(Grade)별로 그룹화
var groupedByGrade = weaponDatabase.GroupBy(weapon => weapon.Grade);
// var를 사용하면 복잡한 타입을 컴파일러가 추론해줍니다.
// 실제 타입은 IEnumerable<IGrouping<WeaponGrade, Weapon>> 입니다.

foreach (IGrouping<WeaponGrade, Weapon> group in groupedByGrade)
{
    Debug.Log($"--- {group.Key} 등급 무기 ---"); // group.Key는 그룹화 기준 값 (여기서는 WeaponGrade)
    foreach (Weapon weaponInGroup in group) // group 자체도 Weapon 컬렉션처럼 사용 가능
    {
        Debug.Log(weaponInGroup.Name);
    }
}
  1. Take() & Skip() - 특정 개수만큼 요소 선택/제외
    Take(N): 시퀀스의 처음부터 N개의 요소를 가져옵니다.
    Skip(N): 시퀀스의 처음부터 N개의 요소를 건너뛰고 나머지를 가져옵니다.
// 예: 가격이 가장 비싼 상위 3개 무기 가져오기
List<Weapon> top3ExpensiveWeapons = weaponDatabase
                                    .OrderByDescending(w => w.Price)
                                    .Take(3)
                                    .ToList();

Debug.Log("--- 가장 비싼 무기 Top 3 ---");
foreach (Weapon w in top3ExpensiveWeapons)
{
    Debug.Log(w.ToString());
}

// 예: 가격이 가장 비싼 상위 3개를 제외한 나머지 무기 가져오기
List<Weapon> exceptTop3ExpensiveWeapons = weaponDatabase
                                          .OrderByDescending(w => w.Price)
                                          .Skip(3)
                                          .ToList();

Debug.Log("--- Top 3 제외 나머지 비싼 순 무기 ---");
foreach (Weapon w in exceptTop3ExpensiveWeapons)
{
    Debug.Log(w.ToString());
}

Ⅴ. LINQ 메서드 체이닝: 강력함의 비결 🔗
LINQ의 가장 큰 장점 중 하나는 여러 메서드를 점(.)으로 연결하여 복잡한 쿼리를 간결하게 작성할 수 있다는 것입니다 (메서드 체이닝).

// 예: 가격이 1000 이상이고, 등급이 Rare 이상인 무기들을, 공격력 높은 순으로 정렬하여, 이름만 가져오기
List<string> filteredAndSortedWeaponNames = weaponDatabase
    .Where(w => w.Price >= 1000 && w.Grade >= WeaponGrade.Rare) // 필터링
    .OrderByDescending(w => w.AttackPower)                     // 정렬
    .Select(w => w.Name)                                       // 이름만 선택
    .ToList();                                                 // 리스트로 변환

Debug.Log("--- 1000원 이상, Rare 등급 이상, 공격력 높은 순, 무기 이름 ---");
foreach (string name in filteredAndSortedWeaponNames)
{
    Debug.Log(name);
}

Ⅵ. 실전 활용 예시: UI에 필터링된 아이템 목록 표시하기
강의 후반부에는 ItemUIManager 스크립트의 UpdateWeaponList(List weapons) 함수를 호출하여, LINQ로 필터링하고 정렬된 무기 목록을 실제 UI에 표시하는 예시를 보여주셨습니다.

 // 링크 테스트 스크립트 내에서 UI 매니저와 연결 후 사용
public ItemUIManager uiManager; // 인스펙터에서 연결

void ShowFilteredItems()
{
    if (uiManager == null) return;

    // 예: 가격이 2000 이상인 무기들만, 가격 낮은 순으로 정렬해서 UI에 표시
    List<Weapon> weaponsToShow = weaponDatabase
                                 .Where(w => w.Price >= 2000)
                                 .OrderBy(w => w.Price)
                                 .ToList();
    uiManager.UpdateWeaponList(weaponsToShow); // UI 매니저의 함수로 전달
}

// 유니티 에디터에서 버튼 클릭 등으로 ShowFilteredItems() 함수를 실행하면
// 조건에 맞는 아이템들만 UI에 정렬되어 나타나는 것을 확인할 수 있습니다. 

Ⅶ. 마치며: LINQ는 여러분의 코드를 우아하게 만들어 줄 거예요! ✨
LINQ는 처음에는 람다식과 다양한 메서드들 때문에 조금 어렵게 느껴질 수 있지만, 한번 익숙해지면 정말 강력하고 편리한 도구입니다. 반복적인 데이터 처리 코드를 줄여주고, 코드의 가독성을 높여주며, 복잡한 요구사항도 손쉽게 구현할 수 있도록 도와줍니다.

강의에서 배운 내용들을 바탕으로 직접 다양한 조건으로 데이터를 조회하고 조작하는 연습을 충분히 해보세요. 특히 아이템이 수백, 수천 개가 되는 실제 게임에서는 LINQ의 진가를 더욱 크게 느끼실 수 있을 거예요!

(강의 자료에는 First(), FirstOrDefault(), Join() 등 더 많은 LINQ 기능들이 언급되었으니, 추가 학습을 통해 더 깊이 파고들어 보세요!)

profile
개발 입문자

0개의 댓글