1. 소개
필터링이란 컬렉션에서 어떤 조건에 맞는 요소만 뽑아내는 거야. 상상해봐: 네 데이터베이스에 10 000개의 상품이 있는데, 매니저가 "딱" 12개만, 새로운 까다로운 기준에 맞는 것만 필요하대. 매번 직접 반복문 돌리고 조건 체크하는 건 귀찮고, 코드도 점점 더러워지지. LINQ가 이런 귀찮은 일 대신 해줘.
실제 앱에서 필터링은 데이터 다룰 때 제일 많이 쓰는 작업이야: 유저한테 필요한 데이터만 보여주고, 컬렉션에서 필요 없는 요소는 빼고, 리포트나 검색, 이메일 보낼 때도 뽑아서 쓰지. 면접에서도 LINQ 필터링 질문은 진짜 자주 나와. 이거 제대로 이해하면 .NET 개발자 입문할 때 완전 든든해.
2. Where 메서드 알아보기
의미와 동작 원리
Where 메서드는 LINQ의 확장 메서드로, 필터링 조건을 정하는 함수(혹은 람다식)를 받아. 이 메서드는 새로운 시퀀스를 반환하는데(원본은 안 건드림!), 그 안에는 조건이 true인 요소만 들어가.
기본 시그니처:
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
generics나 어려운 단어에 겁먹지 마. 지금 중요한 건 전체적인 아이디어야: Where는 원본 컬렉션이랑 네가 정한 조건을 받아. 실제로 써보면 진짜 간단해:
- source — 필터링할 컬렉션 (예: List<Product>)
- predicate — 함수/조건 (예: p => p.Price > 100)
간단한 예제
예를 들어, 상품 리스트가 있는데 가격이 100보다 큰 것만 고르고 싶어. 옛날식이면 foreach로 돌면서 직접 새 리스트에 추가했겠지. LINQ면 한 줄이면 끝이야.
3. 필터링
우리 Product 클래스랑 시작 컬렉션
지난 모듈에서 이런 식으로 상품 클래스를 만들었었지:
class Product
{
public string Name { get; set; }
public double Price { get; set; }
// 화면에 예쁘게 출력하려고:
public override string ToString()
{
return $"{Name} (가격: {Price})";
}
}
상품 리스트가 있다고 해보자:
var products = new List<Product>
{
new Product { Name = "빵", Price = 30 },
new Product { Name = "우유", Price = 87 },
new Product { Name = "치즈", Price = 250 },
new Product { Name = "초콜릿", Price = 130 }
};
Where로 100보다 비싼 상품 필터링
var expensiveProducts = products.Where(p => p.Price > 100);
foreach (var product in expensiveProducts)
{
Console.WriteLine(product);
}
내부적으로 일어나는 일:
- Where가 products 리스트의 모든 요소를 하나씩 보고,
- 각 요소마다 함수(여기선 p => p.Price > 100)를 실행하고,
- 함수가 true를 반환하면 그 상품이 새 컬렉션에 들어가.
화면에 출력되는 결과:
치즈 (가격: 250)
초콜릿 (가격: 130)
중요: 원본 리스트 products는 그대로야! LINQ는 네 데이터를 망가뜨리지 않아.
4. 지연 필터링 (Deferred Execution)
재밌고 중요한 포인트: Where의 결과는 실제로 요소에 접근할 때만 계산돼. 예를 들어 foreach 루프가 시작될 때 LINQ가 원본 컬렉션을 돌면서 실시간으로 필터링하지. 결과를 안 쓰면 아무 일도 안 일어나. 이걸 지연 실행(deferred execution)이라고 해.
이게 주는 장점이 많아:
- 메서드를 여러 개 체이닝해서(예: 필터링하고 정렬까지) 첫 번째로 결과를 쓸 때만 실제로 실행돼.
- 메모리 절약: 중간에 불필요한 컬렉션 변환이 없어.
- 필요하면 실행을 취소하거나, 원하는 개수만큼 찾고 중단할 수도 있어.
비주얼 다이어그램
원본 컬렉션 —► Where (조건) —► 필요한 요소만 순회
[ x ] [ x ] [ + ] [ + ] —► p.Price > 100 —► [ + ] [ + ]
(x — 조건에 안 맞는 요소, + — 조건에 맞는 요소)
5. 문법 옵션
클래식 확장 메서드 방식 (Method Syntax)
이 스타일이 우리가 지금 쓰는 거야:
var result = products.Where(p => p.Price > 100);
대안: Query Syntax
LINQ는 SQL 쿼리랑 비슷한 문법도 지원해:
var result = from p in products
where p.Price > 100
select p;
결과는 똑같아!
어떤 문법을 쓸지는 취향 차이지만, method syntax(점 찍는 방식)가 실제 프로젝트나 긴 체이닝에서는 더 자주 보여.
6. 복잡한 필터 조건
여러 조건
논리 연산자(&&, ||, !)도 쓸 수 있어:
// "초콜릿" 빼고 비싼 상품 찾기
var filtered = products.Where(
p => p.Price > 100 && p.Name != "초콜릿"
);
문자열 필터링 (부분 문자열, 대소문자, Contains)
// 이름에 "ㅣ"가 들어간 상품
var lProducts = products.Where(p => p.Name.Contains("ㅣ"));
참고: Contains는 대소문자 구분해! 대소문자 무시하고 싶으면 이렇게:
var lProducts = products.Where(p => p.Name
.ToLower().Contains("ㅣ")); // 이제 "우유"랑 "초콜릿" 둘 다 결과에 들어감
여러 컬렉션으로 필터링
예를 들어, 상품 리스트랑 결제 리스트가 있다고 해보자. 결제 리스트에 있는 상품만 필터링하고 싶으면 Any나 Join 같은 메서드를 쓰는 게 좋아 — 이건 나중에 더 다룰 거야. 어쨌든 LINQ로 훨씬 복잡한 필터도 할 수 있다는 것만 알아둬!
7. 유용한 팁
중첩 필터링과 메서드 체이닝
Where를 여러 번 이어서 쓸 수도 있어:
var filtered = products
.Where(p => p.Price > 100)
.Where(p => p.Name.StartsWith("초"));
근데 조건을 논리 연산자로 한 번에 합치는 게 더 효율적이고 코드도 깔끔해.
내장 비교자와 커스텀 함수
기본 연산자로 부족할 때도 있어. 예를 들어, 문화권까지 고려해서 대소문자 무시하고 문자열 필터링하고 싶을 때. 이럴 땐 비교 메서드를 쓰면 편해:
var rusProducts = products.Where(
p => p.Name.StartsWith("초", StringComparison.OrdinalIgnoreCase)
);
8. 필터링과 사용자 입력
실제로 간단한 커맨드 필터링 만들어보자! 예를 들어, 사용자한테 최소 가격을 물어보고, 그에 맞는 상품만 보여주는 거지.
Console.Write("상품의 최소 가격은? ");
var minPriceStr = Console.ReadLine();
if (double.TryParse(minPriceStr, out double minPrice))
{
var filtered = products.Where(p => p.Price >= minPrice);
foreach (var product in filtered)
{
Console.WriteLine(product);
}
}
else
{
Console.WriteLine("오류: 숫자가 아님.");
}
이제 우리 "미니 앱"이 거의 마트처럼 보이지?
9. Where 쓸 때 흔한 실수와 함정
프로그래밍에서도 인생처럼 함정이 있어. 이런 건 조심하자.
ToList()나 ToArray() 호출 안 함
Where의 결과는 리스트도 아니고 배열도 아니야! IEnumerable<Product> 객체야. foreach에서는 잘 돌아가지만, 인덱스로 접근하거나 컬렉션이 꼭 필요하면 .ToList()나 .ToArray()를 꼭 써:
var filteredList = products.Where(p => p.Price > 100).ToList();
이걸 무시하면 "Collection was modified during enumeration" 같은 에러가 쉽게 나 — 특히 필터링 도중에 원본 컬렉션을 바꾸면.
원본 컬렉션 변경
LINQ 필터링은 지연 실행이라, 순회 중에 원본 리스트에서 요소를 삭제하면 예외가 날 수 있어. 특히 멀티스레드 상황이나, 필터링 중에 컬렉션이 바뀌는 경우 조심해야 해.
Null 값과 조건 함수
컬렉션에 null이 들어있는데, 조건 함수에서 객체 필드에 바로 접근하면 NullReferenceException이 나. 예를 들어:
var filtered = products.Where(p => p.Name.StartsWith("아"));
만약 products에 어떤 요소가 null이면 코드가 터져.
팁: null이 있을 수 있으면 항상 null 체크를 추가하자:
var filtered = products.Where(p => p != null && p.Name.StartsWith("아"));
GO TO FULL VERSION