기본 패턴
csharp
IEnumerable<Player> query = players
.Where(player => player.Score >= 1000)
.OrderByDescending(player => player.Score);
List<Player> snapshot = query.ToList();설명
IEnumerable<T>는 "지금 당장 다 만들어진 컬렉션"보다 "열거 가능한 흐름"에 가깝습니다. 그래서 LINQ 결과를 받으면 실제로는 지연 실행과 materialization 여부를 함께 생각해야 합니다.ToList(),ToArray()를 호출하면 그 시점에 결과를 전부 계산해 메모리에 확정합니다. 이것이 materialization입니다. 반면IEnumerable<T>상태로 두면 나중에 열거할 때마다 다시 계산될 수 있습니다.- 중요한 판단 기준은 세 가지입니다. 결과를 여러 번 재사용할 것인가, 원본 컬렉션이 이후 바뀔 수 있는가, 계산 비용이 큰가. 이 셋 중 하나라도 강하게 작용하면 materialization이 오히려 더 명확할 수 있습니다.
- 반대로 한 번만 순회하고 끝나는 흐름이라면 굳이 조기
ToList()를 넣지 않는 편이 메모리와 의도를 더 깔끔하게 유지합니다. - LINQ 성능 문제의 상당수는 쿼리 문법보다 "언제 확정하고 언제 미루는가"에서 나옵니다. 그래서
IEnumerable<T>와List<T>의 차이는 타입 선택이 아니라 평가 전략 선택으로 보는 편이 좋습니다.
빠른 정리
| 선택 | 의미 |
|---|---|
IEnumerable<T> | 지연 실행 가능, 열거 시 계산 |
List<T> | 결과를 지금 확정한 스냅샷 |
ToList() | materialization |
| 잘 맞는 경우 | 재사용, 스냅샷 필요, 계산 비용 큼 |
| 피해야 할 습관 | 이유 없는 조기 ToList() |
주의할 점
IEnumerable<T>를 여러 번 순회하면 매번 계산이 다시 일어날 수 있습니다. 반대로 ToList()를 너무 빨리 넣으면
메모리 사용과 전체 계산 비용이 예상보다 커질 수 있으니, 평가 시점을 의식적으로 고르는 편이 중요합니다.
참고 링크
2 sources