Humility

아무리 노력해도 최고가 되지 못할 수 있다⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀그럼에도 노력하는자가 가장 겸손한 것 아닌가

공부하는 블로그

유니티/문법

[Unity] 유니티 C#) LINQ ( Language Integrated Query )

새벽_글쓴이 2025. 1. 14. 23:58
반응형

LINQ란?

C#에서 데이터 쿼리를 위한 통합 프로그래밍 모델로, 다양한 데이터 소스(컬렉션, XML, 데이터베이스 등)에

대해 일관된 방식으로 쿼리를 작성할 수 있게 해주는 기능

 

예를 들어, 도서관에서 책 찾기를 생각하면 쉽다

 

"2000년 이후에 출간된 소설책만 찾아줘"
"가격이 2만원 이하인 책들을 가격 순으로 정렬해줘"
"과학 분야 책들의 평균 가격을 계산해줘"

 

 

복잡한 for문이나 if문여러 번 사용하여 찾을 수도 있지만,
LINQ를 사용하면 더욱 쉽고 직관적으로 처리할 수 있다.

 

예시

// 체력이 50% 이하인 적 캐릭터들 찾기
var weakEnemies = enemies.Where(enemy => enemy.health < 50);

// 점수가 높은 순서대로 플레이어 정렬하기
var topPlayers = players.OrderByDescending(player => player.score);

 

쉽게 말해 "데이터를 검색하고 정리하는 만능 도구" 정도로 이해하면 쉽다


LINQ의 문법

LINQ의 사용할 때는 2가지의 방법이 있다

 

1. 쿼리구문 ( Query Syntax )

var result = from item in items
             where item.price < 1000
             select item;

 

특징

영어 문장처럼 읽히는 방식이다
"items에서 가격이 1000원 미만인 것을 선택해줘" 라고 읽는 느낌
SQL문과 비슷한 형태라서 데이터베이스 개발자들이 친숙하게 느낀다

 

 

2. 메서드 구문 ( Method Syntax )

var result = items.Where(item => item.price < 1000);

 

특징

메서드를 체인처럼 연결하는 방식이다
점(.)으로 메서드들을 이어붙이다
C# 개발자들이 더 선호하는 방식이다

 

실제 예시

게임에서 체력이 50이상이고 레벨순으로 정렬된 플레이어 찾기
// 1. 쿼리 구문
var players1 = from player in allPlayers
               where player.health >= 50
               orderby player.level
               select player;

// 2. 메서드 구문
var players2 = allPlayers
    .Where(player => player.health >= 50)
    .OrderBy(player => player.level);

 

두 방식 모두 같은 결과를 만들어내며, 개인의 취향에 따라 선택하면 된다

단, 메서드 구문더 간결하고 더 많은 기능을 사용할 수 있으며, 디버깅이 더 쉽다는 장점이 있다


LINQ의 실행 방식

LINQ에는 2가지 실행 방식이 있다

 

1. 지연 실행 ( 기본 동작 )

// 이 시점에서는 실제로 필터링이 실행되지 않음
var query = numbers
    .Where(x => x > 5)
    .Select(x => x * 2);

 

2. 즉시 실행

// ToList()를 호출하는 순간 실제로 필터링이 실행됨
var result = numbers
    .Where(x => x > 5)
    .Select(x => x * 2)
    .ToList();

 

실생활에 비유하자면
지연 실행"장보기 목록 작성"
즉시 실행"실제로 장 보러 가는 것"

 

LINQ의 지연 실행에서 중요한 점

실제로 결과값이 필요한 시점에 실행된다는 것

즉, 정의만 하고 실제로 사용하지 않으면 영원히 실행되지 않는다.
실제 데이터가 필요한 시점 (foreach, ToList(), First() 등을 호출할 때) 에 실행되는 것이다

 

예시

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// 지연 실행 확인용 코드
var query = numbers.Where(x => {
    Console.WriteLine($"체크 중: {x}");  // 이 줄은 아직 실행 안됨
    return x > 2;
});

Console.WriteLine("쿼리 만들기 완료");  // 이 줄만 실행됨

// 아무것도 하지 않으면 위의 Where 필터링은 실행되지 않음

// 실제로 데이터가 필요한 시점(foreach 등)에서 실행됨
foreach(var num in query) {
    // 이 시점에서 "체크 중: ..." 메시지들이 출력됨
    Console.WriteLine($"결과: {num}");
}

 

이것은 LINQ의 지연 실행에서 굉장히 주의해야 할 중요한 부분이다

 

예시

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// 쿼리 정의
var query = numbers.Where(x => x > 2);

// numbers 리스트 수정
numbers.Add(6);
numbers.Remove(3);

// 이 시점에서 쿼리 실행
foreach(var num in query)    
{
    Console.WriteLine(num);
}

// 결과: 4, 5, 6

 

실행한 시점에서의 값을 가져오기에 데이터를 잃어버리거나

내가 원하는 값이 아닐 수 있다는 점이다

 

이러한 상황을 방지하려면?

즉시 실행으로 변환
// 쿼리 정의하자마자 List로 변환
var result = numbers.Where(x => x > 2).ToList();

// numbers 리스트를 수정해도 result는 영향 받지 않음
numbers.Add(6);
numbers.Remove(3);

 

원본 데이터 복사 후 사용
// 원본 데이터 복사
var numbersCopy = new List<int>(numbers);

var query = numbersCopy.Where(x => x > 2);

// 원본 numbers를 수정해도 query는 영향 받지 않음
numbers.Add(6);

 

지연 실행의 장점

  • 필요한 시점까지 실행을 미뤄서 성능 향상
  • 쿼리를 여러 번 재사용 가능
  • 메모리 효율성 향상

 

즉시 실행이 필요한 주요 메서드

  • ToList()
  • ToArray()
  • ToDictionary()
  • Count()
  • Sum()
  • First()
  • Last()
  • Max()
  • Min()

LINQ의 메서드

필터링 (Filtering) 메서드
.Where()      // 조건에 맞는 요소 선택
.Where(x => x.age > 20)    // 나이가 20살 초과인 플레이어 선택

.OfType<T>()  // 특정 타입의 요소만 선택
.OfType<Enemy>()    // Enemy 타입의 객체만 선택

.Skip()       // 지정된 수만큼 요소 건너뛰기
.Skip(5)      // 처음 5개의 요소를 건너뛰고 나머지 선택

.Take()       // 지정된 수만큼 요소 가져오기
.Take(3)      // 처음 3개의 요소만 선택

.TakeWhile()  // 조건이 참인 동안만 요소 가져오기
.TakeWhile(x => x.score > 0)    // 점수가 0보다 큰 동안만 요소 선택

.SkipWhile()  // 조건이 참인 동안 요소 건너뛰기
.SkipWhile(x => x.score < 50)   // 점수가 50 미만인 요소들을 건너뛰기

 

정렬 (Ordering) 메서드
.OrderBy()    // 오름차순 정렬
.OrderBy(x => x.name)    // 이름을 알파벳 순으로 정렬

.OrderByDescending()   // 내림차순 정렬
.OrderByDescending(x => x.score)    // 점수를 높은 순으로 정렬

.ThenBy()     // 2차 오름차순 정렬
.ThenBy(x => x.age)    // 같은 이름일 경우 나이순으로 정렬

.ThenByDescending()   // 2차 내림차순 정렬
.ThenByDescending(x => x.level)    // 같은 점수일 경우 레벨 높은순으로 정렬

 

그룹화 (Grouping) 메서드
.GroupBy()    // 특정 속성으로 그룹화
.GroupBy(x => x.category)    // 카테고리별로 그룹 만들기

.ToLookup()   // 즉시 그룹화하여 조회용 형태로 변환
.ToLookup(x => x.type)    // 타입별로 즉시 그룹화하여 조회 가능한 형태로 변환

 

집계 (Aggregation) 메서드
.Count()      // 요소 개수 세기
.Count(x => x.isActive)    // 활성화된 항목의 개수 세기

.Sum()        // 합계 구하기
.Sum(x => x.value)    // 모든 값의 합계 구하기

.Average()    // 평균 구하기
.Average(x => x.score)    // 모든 점수의 평균 구하기

.Max()        // 최대값 구하기
.Max(x => x.height)    // 가장 큰 키 값 찾기

.Min()        // 최소값 구하기
.Min(x => x.height)    // 가장 작은 키 값 찾기

 

투영 (Projection) 메서드
.Select()     // 특정 속성 선택
.Select(x => x.name)    // 이름만 선택하여 새로운 컬렉션 만들기

.SelectMany() // 중첩 컬렉션 펼치기
.SelectMany(x => x.items)    // 각 플레이어가 가진 아이템들을 하나의 컬렉션으로 펼치기

 

집합 (Set) 메서드
.Distinct()   // 중복 제거
.Distinct()    // 중복된 요소들을 제거하고 유일한 값만 선택

.Union()      // 합집합 (중복 제거)
.Union(secondList)    // 두 리스트를 합치고 중복 제거

.Intersect()  // 교집합
.Intersect(secondList)    // 두 리스트에서 공통된 요소만 선택

.Except()     // 차집합
.Except(secondList)    // 첫 번째 리스트에서 두 번째 리스트에 있는 요소 제거

 

요소 접근 메서드
.First()      // 첫 요소 가져오기
.First(x => x.id == 1)    // ID가 1인 첫 번째 요소 가져오기

.Last()       // 마지막 요소 가져오기
.Last(x => x.active)    // 활성화된 마지막 요소 가져오기

.Single()     // 단일 요소 가져오기
.Single(x => x.id == 5)    // ID가 5인 요소 하나만 가져오기 (여러 개면 에러)

.ElementAt()  // 특정 위치 요소 가져오기
.ElementAt(3)    // 네 번째 위치의 요소 가져오기

 

변환 메서드
.ToList()     // List로 변환
.ToList()    // IEnumerable을 List로 변환

.ToArray()    // 배열로 변환
.ToArray()    // IEnumerable을 배열로 변환

.ToDictionary()   // Dictionary로 변환
.ToDictionary(x => x.id)    // ID를 키로 하는 Dictionary로 변환

 

판단 메서드
.Any()        // 요소 존재 여부 확인
.Any(x => x.price > 1000)    // 가격이 1000 초과인 요소가 있는지 확인

.All()        // 모든 요소 조건 만족 여부
.All(x => x.score >= 0)    // 모든 점수가 0 이상인지 확인

.Contains()   // 특정 요소 포함 여부
.Contains(item)    // 특정 아이템이 리스트에 있는지 확인

실제 활용 예시

활성화 된 적 찾기
var activeEnemies = gameObjects
    .Where(enemy => enemy.activeInHierarchy)
    .ToList();

 

가까운 아이템 찾기
var nearbyItems = items
    .Where(item => Vector3.Distance(player.position, item.position) < radius)
    .OrderBy(item => Vector3.Distance(player.position, item.position))
    .ToList();

 

특정 태그를 가진 오브젝트 그룹화
var groupedObjects = gameObjects
    .GroupBy(obj => obj.tag)
    .ToDictionary(g => g.Key, g => g.ToList());

 

※ 디버깅 TIP

// 중간 결과 확인
var result = collection
    .Where(x => x.value > 10)
    .Select(x => x.name)
    .ToList()  // 중간 결과를 확인하기 위해 실체화
    .Where(name => name.StartsWith("A"));
반응형