C# 응용: 람다 식과 LINQ 기초
안녕하세요! 저는 여러분의 코딩 길잡이, 재준봇입니다.
자, 다들 준비되셨나요? 오늘 배울 내용은 C#의 꽃이라고 불리는 람다 식과 LINQ입니다. 아마 이 내용을 배우고 나면 여러분은 “와, 지금까지 내가 짠 코드는 다 뭐였지?”라는 생각이 드실 겁니다. 코딩의 세계에는 ‘노가다’라는 말이 있죠. 오늘 제가 알려드릴 내용은 그 노가다를 획기적으로 줄여주는 마법 같은 도구들입니다.
어렵게 생각하지 마세요. 제가 아주 찰떡같은 비유로, 여러분의 뇌에 직접 때려 박아 드리겠습니다. 가시죠!
12강: C# 응용: 람다 식과 LINQ 기초
보통 코딩을 처음 배우면 우리는 무언가를 찾거나 정렬할 때 반복문을 정말 많이 씁니다. 하지만 실무에서는 반복문을 일일이 쓰는 것을 지양하는 편입니다. 왜냐고요? 코드가 너무 길어지고 읽기 힘들기 때문이죠. 이때 등장하는 구원투수가 바로 람다 식과 LINQ입니다.
1. 람다 식 (Lambda Expression): 코딩계의 포스트잇
먼저 람다 식부터 살펴봅시다. 람다 식이 대체 뭐냐고요?
쉽게 비유해 볼게요. 여러분이 요리책을 본다고 칩시다. 어떤 요리는 설명이 아주 길어요. “냄비를 준비하고, 물을 500ml 붓고, 불을 켜고, 물이 끓으면 면을 넣으세요.” 이게 기존의 함수(Method) 방식입니다.
반면에 람다 식은 그냥 포스트잇에 “물 끓으면 면 넣어!”라고 딱 한 줄 적어놓는 것과 같습니다. 굳이 거창하게 함수라는 이름을 붙여서 만들지 않고, 필요한 그 자리에서 즉석으로 만들어 쓰는 익명 함수인 셈이죠.
왜 람다 식을 쓰나요?
- 코드의 간결함: 함수를 정의하기 위해 클래스 여기저기를 돌아다닐 필요가 없습니다.
- 가독성 향상: 로직이 실행되는 위치에서 바로 정의하기 때문에 흐름을 파악하기 좋습니다.
- 함수형 프로그래밍: 함수를 변수처럼 주고받을 수 있게 됩니다.
람다 식의 기본 문법
람다 식의 핵심은 화살표 => 입니다. 이 화살표를 “goes to”라고 읽으세요.
입력 파라미터 => 실행 내용
예를 들어, 숫자 x를 받아서 x 곱하기 2를 반환하는 람다 식은 이렇게 씁니다.
(x) => x * 2
2. LINQ (Language Integrated Query): 데이터 전용 마법 진공청소기
이제 람다 식의 짝꿍, LINQ를 알아봅시다. LINQ는 ‘언어 통합 쿼리’라는 거창한 이름이 붙어 있지만, 쉽게 말하면 “데이터 뭉치에서 내가 원하는 것만 쏙쏙 뽑아내는 마법 진공청소기”입니다.
원래 데이터베이스(DB)에서 데이터를 뽑아오려면 SQL이라는 별도의 언어를 배워야 했습니다. 그런데 마이크로소프트가 “야, C# 안에서 그냥 SQL처럼 쿼리를 날리면 편하겠는데?” 해서 만든 것이 바로 LINQ입니다.
LINQ를 사용하면 리스트, 배열, XML 등 어떤 데이터 집합이든 상관없이 동일한 방식으로 데이터를 필터링하고, 정렬하고, 변환할 수 있습니다.
3. [실습] 데이터 필터링: 3가지 방법으로 구현하기
자, 이제 여기서 중요합니다. 제가 앞서 말씀드린 “노가다”와 “마법”의 차이를 직접 보여드릴게요. 상황은 이렇습니다. “숫자 리스트에서 50보다 큰 숫자만 골라내어 새로운 리스트에 담고 싶다”는 조건입니다.
방법 1: 전통적인 for 문 (가장 원시적인 방법)
가장 기초적인 방식입니다. 인덱스를 하나하나 체크하며 조건에 맞는지 확인합니다.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<int> numbers = new List<int> { 10, 60, 30, 80, 40, 110 };
List<int> result = new List<int>();
// for문을 이용한 필터링
for (int i = 0; i < numbers.Count; i++)
{
// 1. 현재 인덱스의 값이 50보다 큰지 확인합니다.
if (numbers[i] > 50)
{
// 2. 조건에 맞다면 결과 리스트에 추가합니다.
result.Add(numbers[i]);
}
}
// 결과 출력
foreach (var n in result) Console.WriteLine(n);
}
}
분석:
for (int i = 0; i < numbers.Count; i++): 리스트의 처음부터 끝까지 인덱스를 통해 접근합니다.if (numbers[i] > 50): 일일이 값을 꺼내서 비교합니다.result.Add(...): 조건에 맞는 값만 따로 저장합니다.- 단점: 코드가 길고, 인덱스 실수(
IndexOutOfRangeException)가 날 가능성이 있습니다.
방법 2: foreach 문 (조금 더 세련된 방법)
인덱스 관리의 번거로움을 없앤 방식입니다.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<int> numbers = new List<int> { 10, 60, 30, 80, 40, 110 };
List<int> result = new List<int>();
// foreach문을 이용한 필터링
foreach (int number in numbers)
{
// 1. 리스트의 각 요소를 하나씩 가져와서 50과 비교합니다.
if (number > 50)
{
// 2. 조건에 맞으면 추가합니다.
result.Add(number);
}
}
foreach (var n in result) Console.WriteLine(n);
}
}
분석:
foreach (int number in numbers): 인덱스 없이 리스트 안의 요소를 직접 하나씩 꺼냅니다.if (number > 50): 꺼낸 값이 50보다 큰지 확인합니다.- 단점:
for문보다는 깔끔하지만, 여전히 “어떻게(How)” 데이터를 뽑아낼지를 일일이 명령해야 합니다.
방법 3: LINQ와 람다 식 (마법의 방법)
이제 대망의 LINQ입니다. “어떻게”가 아니라 “무엇을(What)” 원하는지만 말하면 됩니다.
using System;
using System.Collections.Generic;
using System.Linq; // LINQ를 쓰려면 반드시 이 네임스페이스가 필요합니다!
public class Program
{
public static void Main()
{
List<int> numbers = new List<int> { 10, 60, 30, 80, 40, 110 };
// LINQ의 Where 메서드와 람다 식을 사용합니다.
var result = numbers.Where(n => n > 50).ToList();
// 결과 출력
result.ForEach(n => Console.WriteLine(n));
}
}
분석:
numbers.Where(...): “numbers 리스트에서 어디(Where)에 해당하는 것만 골라내!”라는 뜻입니다.n => n > 50: 이것이 바로 람다 식입니다. “n이라는 숫자를 받아서 n이 50보다 큰 경우만 True로 쳐줘!”라는 조건식입니다..ToList(): LINQ의 결과는 기본적으로 지연 실행(Deferred Execution) 상태입니다. 이를 실제 리스트 형태로 확정 짓기 위해 리스트로 변환합니다.result.ForEach(...): 출력조차 람다 식으로 처리하여 반복문 없이 한 줄로 끝냈습니다.
4. LINQ의 핵심 연산자 4대장
LINQ에는 정말 많은 기능이 있지만, 실무에서 90% 이상 사용하는 4가지만 완벽하게 익히세요.
1) Where: 필터링 (걸러내기)
조건에 맞는 데이터만 남깁니다.
- 예:
users.Where(u => u.Age >= 20)(20세 이상 사용자만 추출)
2) Select: 변환 (모양 바꾸기)
데이터의 형태를 바꿉니다. 예를 들어 사용자 객체 전체가 아니라 ‘이름’만 뽑아내고 싶을 때 씁니다.
- 예:
users.Select(u => u.Name)(사용자 리스트 $\rightarrow$ 이름 리스트로 변환)
3) OrderBy / OrderByDescending: 정렬 (줄 세우기)
오름차순이나 내림차순으로 정렬합니다.
- 예:
products.OrderBy(p => p.Price)(가격 낮은 순 정렬) - 예:
products.OrderByDescending(p => p.Price)(가격 높은 순 정렬)
4) FirstOrDefault: 하나만 가져오기
조건에 맞는 첫 번째 요소를 가져옵니다. 만약 없으면 기본값(null 등)을 반환합니다.
- 예:
users.FirstOrDefault(u => u.Id == 1)(ID가 1인 사용자 한 명만 찾기)
5. [실전 응용] 종합 선물 세트 예제
이번에는 실제 프로젝트에서 쓸 법한 시나리오를 짜보겠습니다. 학생들의 성적 리스트가 있고, 여기서 “80점 이상인 학생들만 뽑아서, 성적순으로 내림차순 정렬한 뒤, 이름만 출력”하는 프로그램을 만들어 보겠습니다.
using System;
using System.Collections.Generic;
using System.Linq;
public class Student
{
public string Name { get; set; }
public int Score { get; set; }
}
public class Program
{
public static void Main()
{
// 데이터 준비
List<Student> students = new List<Student>
{
new Student { Name = "김철수", Score = 70 },
new Student { Name = "이영희", Score = 95 },
new Student { Name = "박지성", Score = 85 },
new Student { Name = "최유리", Score = 60 },
new Student { Name = "정재준", Score = 100 }
};
// LINQ 체이닝 (연쇄 호출)
var topStudentsNames = students
.Where(s => s.Score >= 80) // 1. 80점 이상만 필터링
.OrderByDescending(s => s.Score) // 2. 점수 높은 순으로 정렬
.Select(s => s.Name) // 3. 이름만 추출
.ToList(); // 4. 리스트로 변환
Console.WriteLine("우수 학생 명단:");
topStudentsNames.ForEach(name => Console.WriteLine(name));
}
}
코드 뜯어보기:
Where(s => s.Score >= 80): 여기서 80점 미만인 철수와 유리가 탈락합니다. [영희, 지성, 재준] 남음.OrderByDescending(s => s.Score): 남은 학생들을 점수순으로 줄 세웁니다. [재준(100), 영희(95), 지성(85)] 순서로 정렬됩니다.Select(s => s.Name): 이제 점수는 필요 없고 이름만 필요합니다. [ “정재준”, “이영희”, “박지성” ] 문자열 리스트가 됩니다.ToList(): 최종 결과물을 리스트에 담아 완성합니다.
6. 초보자 폭풍 질문!
Q: 선생님, 람다 식
n => n > 50에서 앞에 있는n은 도대체 어디서 튀어나온 건가요? 제가 정의한 적이 없는데 왜 쓸 수 있죠?
재준봇의 답변:
아주 좋은 질문입니다! 여기서 n은 “이름 없는 임시 변수”라고 생각하세요. Where라는 메서드가 내부적으로 리스트의 요소를 하나씩 꺼내서 “자, 여기 요소 하나 줄게. 이걸 n이라고 부르기로 하자. 이제 이 n을 가지고 조건을 만들어봐!”라고 우리에게 제안하는 것입니다.
따라서 n 대신 x라고 쓰든, apple이라고 쓰든 상관없습니다. Where(apple => apple > 50)이라고 써도 똑같이 동작합니다. 그냥 “지금 처리하고 있는 그 녀석”을 가리키는 별명이라고 이해하시면 됩니다!
7. 실무주의보
주의: LINQ를 너무 남발하면 성능이 떨어질 수 있습니다!
상세 설명:
LINQ는 정말 편리하지만, 내부적으로는 foreach 문보다 약간 더 많은 오버헤드(추가 연산)가 발생합니다. 특히 수백만 건의 거대한 데이터를 처리할 때는 LINQ보다 전통적인 for 문이 훨씬 빠를 수 있습니다.
또한, ToList()를 호출하기 전까지는 쿼리가 실제로 실행되지 않는 ‘지연 실행’ 특성이 있습니다. 만약 루프 안에서 계속 LINQ 쿼리를 생성하고 ToList()를 호출하면 메모리 낭비가 심해질 수 있으니 주의해야 합니다.
결론: 데이터 양이 적당하다면 가독성을 위해 LINQ를 쓰시고, 극강의 성능이 필요한 알고리즘 구현 단계에서는 전통적인 반복문을 고려하세요!
마무리하며
자, 오늘 우리는 C#의 꽃인 람다 식과 LINQ에 대해 배웠습니다. 처음에는 => 이런 기호가 낯설고, 메서드를 줄줄이 이어 붙이는 체이닝 방식이 생소하시겠지만, 딱 3번만 직접 타이핑해 보세요. 어느 순간 여러분의 손가락이 자동으로 Where를 치고 있을 겁니다.
이제 여러분은 더 이상 수십 줄의 for 문에 갇혀 있을 필요가 없습니다. 세련되게, 트렌디하게, 딱 한 줄로 데이터를 다루는 C# 개발자로 거듭나신 것을 축하드립니다!
궁금한 점이 있다면 언제든 댓글 남겨주세요. 재준봇이 찰떡같이 답변해 드리겠습니다. 고생하셨습니다!
<hr>