핵심 정리
List<string> highScores = players
.Where(p => p.Score >= 1000) // 필터링
.OrderByDescending(p => p.Score) // 정렬
.Select(p => $"{p.Name}: {p.Score}") // 변환
.ToList(); // 즉시 실행 + List로 확정기본 흐름
어떤 LINQ 연산자를 먼저 알면 되나
입문 단계에서는 LINQ를 아래 네 묶음으로 보면 대부분의 조회 코드를 읽을 수 있습니다.
var filtered = users.Where(u => u.IsActive); // 필터링
var names = users.Select(u => u.Name); // 변환
var sorted = users.OrderBy(u => u.Name); // 정렬
bool hasAny = users.Any(); // 즉시 실행Where: 남길 항목 고르기Select: 다른 형태로 바꾸기OrderBy/ThenBy: 정렬하기Any,Count,First,ToList: 결과 확정하기
LINQ는 extension method의 집합
LINQ의 Where, Select, OrderBy 등은 System.Linq.Enumerable 클래스의 extension method입니다. using System.Linq;를 추가하면 IEnumerable<T>를 구현하는 모든 타입에서 사용할 수 있습니다.
// 이 두 호출은 완전히 동일
var r1 = players.Where(p => p.Score >= 1000);
var r2 = Enumerable.Where(players, p => p.Score >= 1000);지연 실행 — 열거할 때 계산된다
LINQ 체인을 작성한다고 해서 즉시 실행되지 않습니다. foreach, ToList(), ToArray(), Count() 같은 열거 트리거가 호출될 때 비로소 실행됩니다. 이것을 지연 실행(deferred execution) 이라고 합니다.
var query = players.Where(p => p.Score >= 1000); // 아직 실행 안 됨
players.Add(new Player("Late", 2000)); // 원본에 추가
foreach (var p in query) // 이 순간 실행 — Late도 포함됨
Console.WriteLine(p.Name);
// ToList()는 이 시점의 결과를 스냅샷으로 확정
var snapshot = query.ToList(); // 즉시 실행, 결과 고정// ❌ 지연 실행의 함정 — 매번 다시 계산
IEnumerable<Player> top = players.Where(p => p.Score >= 1000);
Console.WriteLine(top.Count()); // 쿼리 실행
Console.WriteLine(top.First()); // 쿼리 다시 실행 — 비효율
// ✅ ToList()로 한 번만 실행
List<Player> top = players.Where(p => p.Score >= 1000).ToList();핵심 연산자
// Where — 조건을 만족하는 항목만 남김
var adults = users.Where(u => u.Age >= 18);
// Select — 각 항목을 다른 형태로 변환
var names = users.Select(u => u.Name);
var dtos = users.Select(u => new UserDto(u.Id, u.Name));
// OrderBy / OrderByDescending
var sorted = users.OrderBy(u => u.Name)
.ThenByDescending(u => u.Age); // 보조 정렬
// Any — 조건을 만족하는 항목이 하나라도 있는지 (Count() > 0보다 빠름)
bool hasAdmin = users.Any(u => u.IsAdmin);
// All — 모든 항목이 조건을 만족하는지
bool allAdult = users.All(u => u.Age >= 18);
// First / FirstOrDefault
var first = users.First(u => u.IsAdmin); // 없으면 예외
var first = users.FirstOrDefault(u => u.IsAdmin); // 없으면 null
// Single — 정확히 하나여야 하는 경우
var user = users.Single(u => u.Id == 42); // 없거나 둘 이상이면 예외// ❌ 존재 여부만 볼 때 Count() > 0는 끝까지 셀 수 있다
bool hasAdmin = users.Count(u => u.IsAdmin) > 0;
// ✅ Any()가 의도가 더 직접적이고 보통 더 빠르다
bool hasAdminFast = users.Any(u => u.IsAdmin);쿼리 문법 vs 메서드 문법
C#은 SQL과 유사한 쿼리 문법도 지원합니다. 컴파일러가 메서드 문법으로 변환하므로 결과는 동일합니다.
// 메서드 문법
var result = players.Where(p => p.Score >= 1000).Select(p => p.Name);
// 쿼리 문법 — 같은 결과
var result = from p in players
where p.Score >= 1000
select p.Name;쿼리 문법은 group, join, 복잡한 from이 나올 때 더 읽기 쉬운 경우가 많고, 단순 필터링과 변환은 메서드 문법이 더 짧습니다.
이미지
체크포인트
| 연산자 | 역할 | 즉시/지연 |
|---|---|---|
Where | 필터링 | 지연 |
Select | 변환 | 지연 |
OrderBy / ThenBy | 정렬 | 지연 |
ToList() / ToArray() | 결과 확정 | 즉시 |
Any() | 존재 여부 | 즉시 |
Count() | 개수 | 즉시 |
First() / FirstOrDefault() | 첫 항목 | 즉시 |
주의할 점
LINQ 체인 안에 부작용(상태 변경, 로그 출력, 외부 변수 수정)을 넣지 마세요. 지연 실행 때문에 언제 실행되는지 예측하기 어렵고, 몇 번 실행되는지도 보장되지 않습니다.
"데이터를 어떻게 고르고 바꾸는가"가 주제라면 LINQ가 좋고, "여러 단계의 절차를 어떻게 실행하는가"가 주제라면 루프가 더 나은 경우가 많습니다.
First()는 없으면 예외를 던지고, FirstOrDefault()는 기본값을 돌려줍니다. 값이 반드시 있어야 하는지, 없어도 되는지를 먼저 정하고 선택하세요.
참고 링크
2 sources