LINQ의 대표적인 연산자들을 실제 활용 사례와 함께 정리합니다. 각 메서드의 동작 원리와 코드 예시를 통해 효율적인 데이터 처리 방법을 익혀보세요.
1. Where - 조건 기반 필터링
시퀀스에서 특정 조건을 만족하는 요소만 추출합니다.
var scores = new List<int> { 85, 92, 78, 95, 88 };
// 90점 이상만 선택
var topScores = scores.Where(s => s >= 90);
// 결과: 92, 95
2. OfType - 타입별 분류
비제네릭 컬션에서 원하는 타입만 걸러냅니다.
var mixedData = new ArrayList { 100, "텍스트", 200, "문자열", 300 };
// 정수형 값만 추출
var integers = mixedData.OfType<int>();
// 결과: 100, 200, 300
3. Skip - 앞부분 건너뛰기
시퀀스의 시작 부분에서 지정된 개수만 제외합니다.
var items = new List<string> { "A", "B", "C", "D", "E" };
var skipped = items.Skip(2);
// 결과: "C", "D", "E"
4. Take - 앞부분 추출
시퀀스의 시작부터 지정된 개수만 가져옵니다.
var items = new List<string> { "A", "B", "C", "D", "E" };
var taken = items.Take(3);
// 결과: "A", "B", "C"
5. SkipLast / TakeLast - 끝부분 조작
시퀀스의 마지막 부분을 건너뛰거나 추출합니다(.NET Core 3.0+).
var values = new List<int> { 10, 20, 30, 40, 50 };
var withoutLastTwo = values.SkipLast(2); // 10, 20, 30
var lastTwoOnly = values.TakeLast(2); // 40, 50
6. SkipWhile / TakeWhile - 조건 기반 분할
조건이 참인 동안 요소를 건너뛰거나 가져옵니다.
var temperatures = new List<int> { 15, 18, 22, 25, 20, 16 };
// 20도 미만인 초기값들 건너뛰기
var warmPart = temperatures.SkipWhile(t => t < 20); // 22, 25, 20, 16
// 20도 미만인 초기값들만 가져오기
var coolPart = temperatures.TakeWhile(t => t < 20); // 15, 18
7. Select - 형태 변환
각 요소를 새로운 형태로 매핑합니다.
var employees = new List<Employee> {
new() { FullName = "김철수", Department = "개발팀" },
new() { FullName = "이영희", Department = "디자인팀" }
};
// 이름만 추출
var names = employees.Select(e => e.FullName);
// 결과: "김철수", "이영희"
8. Select with Index - 인덱스 활용 변환
요소의 위치 정보를 함께 활용합니다.
var tasks = new List<string> { "분석", "설계", "구현", "테스트" };
var numberedTasks = tasks.Select((task, idx) => $"{idx + 1}단계: {task}");
// 결과: "1단계: 분석", "2단계: 설계", "3단계: 구현", "4단계: 테스트"
9. SelectMany - 중첩 컬렉션 평탄화
컬션 속의 컬렉션을 하나의 시퀀스로 펼칩니다.
var teams = new List<Team> {
new() { TeamName = "A팀", Members = new[] { "홍길동", "김민수" } },
new() { TeamName = "B팀", Members = new[] { "박지연", "최수진" } }
};
// 모든 팀원을 하나의 목록으로
var allMembers = teams.SelectMany(t => t.Members);
// 결과: "홍길동", "김민수", "박지연", "최수진"
10. SelectMany with Index - 그룹 정보 유지 평탄화
var batches = new List {
new() { 100, 200 },
new() { 300, 400 }
};
var withBatchInfo = batches.SelectMany((batch, batchNo) =>
batch.Select(val => $"[{batchNo}번째 배치] {val}")
);
// 결과: "[0번째 배치] 100", "[0번째 배치] 200", "[1번째 배치] 300", "[1번째 배치] 400"
11. Cast - 강제 형변환
요소를 지정된 타입으로 강제 변환합니다(실패 시 예외 발생).
var objects = new ArrayList { 1.0, 2.0, 3.0 };
var doubles = objects.Cast<double>(); // 1.0, 2.0, 3.0
// 주의: 변환 불가 요소 있으면 InvalidCastException
12. Chunk - 고정 크기 분할
시퀀스를 지정된 크기의 배열들로 나눕니다(.NET 6+).
var data = Enumerable.Range(1, 10);
var chunks = data.Chunk(3);
// 결과: [1,2,3], [4,5,6], [7,8,9], [10]
13. 지연 실행 vs 즉시 실행
LINQ 쿼리는 실제로 데이터가 필요할 때까지 실행되지 않습니다.
var source = new List<int> { 5, 10, 15 };
// 아직 실행되지 않음 (지연 실행)
var query = source.Where(x => x > 5);
source.Add(20); // 쿼리 정의 후 데이터 변경
// 이제 실제 실행됨 (즉시 실행)
var result = query.ToList(); // 10, 15, 20 포함
14. Any - 존재 여부 확인
조건을 만족하는 요소가 하나라도 있는지 검사합니다.
var inventory = new List<Product> { /* ... */ };
bool hasExpensive = inventory.Any(p => p.Price > 100000);
bool hasAnyItem = inventory.Any(); // 비어있지 않은지
15. All - 전체 만족 확인
모든 요소가 조건을 만족하는지 검사합니다.
var grades = new List<int> { 80, 90, 85, 95 };
bool allPassed = grades.All(g => g >= 60); // true
16. Contains - 특정 값 포함 여부
var tags = new List<string> { "C#", "LINQ", "ASP.NET" };
bool hasLinq = tags.Contains("LINQ"); // true
17. Append / Prepend - 단일 요소 추가
시퀀스의 앞이나 뒤에 요소 하나를 추가합니다.
var sequence = new List<int> { 2, 3, 4 };
var withStart = sequence.Prepend(1); // 1, 2, 3, 4
var withEnd = sequence.Append(5); // 2, 3, 4, 5
18. Count - 요소 개수 산출
var orders = new List<Order> { /* ... */ };
int totalCount = orders.Count();
int pendingCount = orders.Count(o => o.Status == "Pending");
19. TryGetNonEnumeratedCount - 최적화된 카운트
열거 없이 컬렉션 크기를 가져올 수 있으면 성공합니다.
var collection = new List<string> { "a", "b", "c" };
if (collection.TryGetNonEnumeratedCount(out int size))
{
Console.WriteLine($"빠른 카운트: {size}"); // 3
}
20. Max / MaxBy - 최대값 추출
var prices = new List<decimal> { 15000m, 23000m, 18000m };
decimal highestPrice = prices.Max();
var products = new List<Product> {
new() { Name = "마우스", Stock = 50 },
new() { Name = "키보드", Stock = 120 }
};
var mostStocked = products.MaxBy(p => p.Stock); // 키보드
21. Min / MinBy - 최소값 추출
var lowestPrice = prices.Min();
var leastStocked = products.MinBy(p => p.Stock); // 마우스
22. Sum - 합계 계산
var monthlySales = new List<int> { 120, 150, 180, 200 };
int totalSales = monthlySales.Sum(); // 650
23. Average - 평균 계산
double averageSales = monthlySales.Average(); // 162.5
24. LongCount - 대용량 카운트
int 범위를 초과하는 컬렉션용입니다.
var hugeData = Enumerable.Range(1, int.MaxValue);
long exactCount = hugeData.LongCount();
25. Aggregate - 사용자 정의 누적
복잡한 누적 연산을 정의합니다.
var nums = new List<int> { 2, 3, 4 };
// 누적 곱셈 (초기값 1)
int factorialLike = nums.Aggregate(1, (acc, n) => acc * n); // 24
var texts = new List<string> { "Hello", "World", "LINQ" };
// 문자열 누적 후 가공
string combined = texts.Aggregate(
new StringBuilder(),
(sb, text) => sb.AppendLine(text),
sb => sb.ToString().Trim()
);
26. First / FirstOrDefault - 첫 요소 추출
var candidates = new List<string> { "후보A", "후보B", "후보C" };
string first = candidates.First();
string firstLong = candidates.FirstOrDefault(c => c.Length > 4) ?? "없음";
27. Single / SingleOrDefault - 유일한 요소 추출
var uniqueId = new List<int> { 42 };
int onlyOne = uniqueId.Single(); // 성공
var empty = new List<int>();
int defaultOrError = empty.SingleOrDefault(); // 0 반환
var duplicates = new List<int> { 1, 1 };
// duplicates.Single(); // InvalidOperationException!
28. Last / LastOrDefault - 마지막 요소 추출
var queue = new List<string> { "첫번째", "중간", "마지막" };
string final = queue.Last();
string lastMatch = queue.LastOrDefault(s => s.Contains("번")); // "첫번째"
29. ElementAt / ElementAtOrDefault - 인덱스 접근
var ranks = new List<string> { "Gold", "Silver", "Bronze" };
string second = ranks.ElementAt(1); // "Silver"
string outOfRange = ranks.ElementAtOrDefault(10) ?? "Unknown"; // "Unknown"
30. DefaultIfEmpty - 빈 시퀀스 대체값
var emptyResults = new List<int>();
var withDefault = emptyResults.DefaultIfEmpty(-1); // -1 하나 포함
31. ToArray / ToList - 컬렉션 변환
var enumerable = Enumerable.Range(1, 5);
int[] arrayForm = enumerable.ToArray();
List<int> listForm = enumerable.ToList();
32. ToDictionary - 사전 변환
var users = new List<User> {
new() { UserId = "user01", UserName = "홍길동" },
new() { UserId = "user02", UserName = "김철수" }
};
var userMap = users.ToDictionary(u => u.UserId, u => u.UserName);
// 접근: userMap["user01"] → "홍길동"
33. ToHashSet - 집합 변환
var withDuplicates = new List<int> { 1, 2, 2, 3, 3, 3 };
HashSet<int> uniqueSet = withDuplicates.ToHashSet(); // 1, 2, 3
34. ToLookup - 다대일 매핑
하나의 키에 여러 값이 매핑되는 구조입니다.
var orders = new List<Order> {
new() { Category = "전자", Product = "노트북" },
new() { Category = "전자", Product = "태블릿" },
new() { Category = "의류", Product = "셔츠" }
};
ILookup byCategory = orders.ToLookup(o => o.Category, o => o.Product);
// byCategory["전자"] → "노트북", "태블릿"
35. Range / Repeat / Empty - 시퀀스 생성
// 연속된 정수
var oneToTen = Enumerable.Range(1, 10); // 1~10
// 반복 값
var fiveStars = Enumerable.Repeat("★", 5); // "★", "★", "★", "★", "★"
// 빈 시퀀스 (타입 안전)
IEnumerable<int> nothing = Enumerable.Empty<int>();
36. Distinct / DistinctBy - 중복 제거
var repeated = new List<int> { 5, 3, 5, 2, 3, 5 };
var unique = repeated.Distinct(); // 5, 3, 2
var people = new List<Person> {
new() { Name = "김철수", City = "서울" },
new() { Name = "이영희", City = "서울" },
new() { Name = "박민수", City = "부산" }
};
var byCity = people.DistinctBy(p => p.City); // 김철수, 박민수
37. Union / Intersect / Except - 집합 연산
var setA = new List<int> { 1, 2, 3, 4 };
var setB = new List<int> { 3, 4, 5, 6 };
var union = setA.Union(setB); // 1, 2, 3, 4, 5, 6 (합집합)
var intersect = setA.Intersect(setB); // 3, 4 (교집합)
var except = setA.Except(setB); // 1, 2 (차집합)
38. UnionBy / IntersectBy / ExceptBy - 키 기반 집합 연산
var oldStock = new List<Item> { new() { Code = "A001", Qty = 10 } };
var newStock = new List<Item> { new() { Code = "A001", Qty = 15 }, new() { Code = "A002", Qty = 20 } };
var merged = oldStock.UnionBy(newStock, i => i.Code); // A001, A002
39. SequenceEqual - 시퀀스 동등 비교
var seq1 = new List<int> { 1, 2, 3 };
var seq2 = new List<int> { 1, 2, 3 };
var seq3 = new List<int> { 1, 3, 2 };
bool same = seq1.SequenceEqual(seq2); // true
bool different = seq1.SequenceEqual(seq3); // false (순서 다름)
40. Zip - 병렬 조합
var ids = new List<int> { 101, 102, 103 };
var names = new List<string> { "김철수", "이영희", "박민수" };
var combined = ids.Zip(names, (id, name) => new { Id = id, Name = name });
// 결과: {101, "김철수"}, {102, "이영희"}, {103, "박민수"}
41. Join - 내부 조인
var customers = new List<Customer> {
new() { CustId = 1, CustName = "A사" },
new() { CustId = 2, CustName = "B사" }
};
var transactions = new List<Transaction> {
new() { CustId = 1, Amount = 50000 },
new() { CustId = 1, Amount = 30000 },
new() { CustId = 2, Amount = 70000 }
};
var salesReport = customers.Join(
transactions,
c => c.CustId,
t => t.CustId,
(c, t) => new { Customer = c.CustName, Sale = t.Amount }
);
// 결과: {A사, 50000}, {A사, 30000}, {B사, 70000}
42. GroupJoin - 그룹 조인
var deptReport = customers.GroupJoin(
transactions,
c => c.CustId,
t => t.CustId,
(c, ts) => new {
Customer = c.CustName,
TotalSales = ts.Sum(t => t.Amount),
Count = ts.Count()
}
);
// 결과: {A사, 80000, 2}, {B사, 70000, 1}
43. Concat - 단순 연결
Union과 달리 중복을 제거하지 않습니다.
var listX = new List<int> { 1, 2, 3 };
var listY = new List<int> { 3, 4, 5 };
var concatenated = listX.Concat(listY); // 1, 2, 3, 3, 4, 5
44. GroupBy - 그룹화
var students = new List<Student> {
new() { Name = "김철수", Grade = "A" },
new() { Name = "이영희", Grade = "B" },
new() { Name = "박민수", Grade = "A" }
};
var byGrade = students.GroupBy(s => s.Grade);
// A: 김철수, 박민수 / B: 이영희
45. OrderBy / OrderByDescending - 정렬
var byNameAsc = students.OrderBy(s => s.Name);
var byGradeDesc = students.OrderByDescending(s => s.Grade);
46. ThenBy / ThenByDescending - 다중 정렬
var multiSort = students
.OrderBy(s => s.Grade)
.ThenBy(s => s.Name);
// 성적 우선, 동일 성적 내 이름순
47. Reverse - 역순
var reversed = students.Select(s => s.Name).Reverse();
48. AsParallel - 병렬 처리
대용량 데이터 처리 시 다중 코어 활용.
var bigData = Enumerable.Range(1, 10_000_000);
var parallelResult = bigData.AsParallel()
.Where(n => n % 7 == 0)
.Select(n => n * n)
.Sum();