C# 이터레이터와 LINQ 정리
C# 이터레이터와 LINQ
1. foreach와 열거자
1-1. foreach
foreach는 배열이나 컬렉션에 저장된 요소를 하나씩 순회하는 반복문이다.
반복 횟수나 인덱스를 직접 관리하지 않아도 되며, 컬렉션의 요소를 처음부터 끝까지 순회할 수 있다.
주요 특징
- 컬렉션의 요소를 순서대로 하나씩 가져온다.
- 요소의 개수를 직접 관리할 필요가 없다.
- 순회할 수 있는 객체에서 사용할 수 있다.
1-2. IEnumerable과 IEnumerator
foreach가 동작하기 위해 필요한 순회 규약이다.
IEnumerable은 순회할 수 있는 대상이다.IEnumerator는 실제 순회를 담당하는 객체다.
주요 멤버
| 멤버 | 역할 |
|---|---|
GetEnumerator() | 열거자를 가져온다. |
MoveNext() | 다음 요소로 이동한다. |
Current | 현재 요소를 가져온다. |
Reset() | 순회 위치를 초기화한다. |
동작 순서
GetEnumerator()로 열거자를 가져온다.MoveNext()로 다음 요소로 이동한다.Current로 현재 요소를 가져온다.- 더 이상 요소가 없을 때까지 반복한다.
1-3. 제네릭 열거자
IEnumerable<T>와 IEnumerator<T>는 요소의 자료형을 지정하는 제네릭 인터페이스다.
기존 인터페이스와 달리 요소를 object로 변환하지 않고 지정된 자료형으로 처리할 수 있다.
실제 C#에서는 제네릭 열거자를 주로 사용한다.
2. 이터레이터
2-1. 이터레이터
이터레이터는 여러 값을 하나씩 반환하는 메서드다.
일반적인 return은 값을 반환하고 메서드를 종료하지만, 이터레이터는 yield return을 사용해 값을 반환한 뒤 실행을 잠시 멈춘다.
다음 값을 요청받으면 이전에 멈춘 위치부터 다시 실행한다.
2-2. yield return
yield return은 이터레이터에서 값을 하나씩 반환하는 키워드다.
주요 특징
- 값을 반환한 뒤 실행을 일시 중단한다.
- 다음 값을 요청받으면 중단된 위치부터 실행한다.
- 모든 값을 미리 생성하지 않아도 된다.
- 필요한 만큼만 값을 생성할 수 있다.
2-3. yield break
yield break는 이터레이터의 실행을 즉시 종료한다.
조건에 따라 더 이상 값을 반환할 필요가 없을 때 사용한다.
2-4. 컬렉션 반환과 이터레이터 비교
| 구분 | 컬렉션 반환 | 이터레이터 |
|---|---|---|
| 값 생성 | 전체 결과를 생성 | 요청할 때 하나씩 생성 |
| 저장 | 컬렉션에 결과 저장 | 전체 결과를 미리 저장하지 않음 |
| 일부 사용 | 전체 결과가 만들어질 수 있음 | 필요한 만큼만 생성 가능 |
| 무한 수열 | 전체 저장이 어려움 | 필요한 만큼 생성 가능 |
3. 지연 실행
3-1. 지연 실행
지연 실행은 실제로 값이 필요한 시점까지 실행을 미루는 방식이다.
이터레이터 메서드는 호출하는 순간 본문이 실행되지 않는다. foreach 등으로 실제 값을 요청할 때 실행된다.
3-2. 지연 실행의 특징
- 메서드를 호출하는 것과 실제 실행하는 시점이 다르다.
- 원본 데이터가 변경되면 순회 결과도 달라질 수 있다.
- 여러 번 순회하면 이터레이터가 반복 실행된다.
- 필요한 시점에 필요한 만큼 데이터를 처리할 수 있다.
3-3. 무한 수열
이터레이터는 무한 반복을 이용해 무한 수열을 표현할 수 있다.
값을 요청하는 쪽에서 break를 사용하면 순회를 중단할 수 있다.
3-4. 지연 실행 시 주의사항
같은 이터레이터를 여러 번 순회하면 내부 계산도 반복될 수 있다.
계산량이 많거나 동일한 결과를 여러 번 사용해야 한다면 결과를 컬렉션에 저장하는 것이 좋다.
4. LINQ
4-1. LINQ의 개념
LINQ(Language Integrated Query)는 컬렉션에 저장된 데이터를 검색하고, 변환하고, 정렬하고, 집계하는 기능이다.
반복문과 조건문을 직접 작성하지 않고도 다양한 연산자를 연결해 데이터를 처리할 수 있다.
4-2. 시퀀스
시퀀스는 순서대로 열거할 수 있는 데이터의 연속이다.
IEnumerable<T>를 구현한 배열, 리스트 등의 컬렉션과 이터레이터가 반환하는 데이터가 시퀀스에 해당한다.
4-3. 주요 LINQ 연산자
| 분류 | 연산자 | 역할 |
|---|---|---|
| 필터링 | Where | 조건에 맞는 요소 선택 |
| 변환 | Select | 요소를 다른 형태로 변환 |
| 정렬 | OrderBy | 오름차순 정렬 |
| 정렬 | OrderByDescending | 내림차순 정렬 |
| 추가 정렬 | ThenBy | 추가 정렬 기준 적용 |
| 집계 | Count | 요소 개수 계산 |
| 집계 | Sum | 합계 계산 |
| 집계 | Average | 평균 계산 |
| 집계 | Min, Max | 최솟값과 최댓값 계산 |
4-4. 요소 선택 연산자
| 연산자 | 역할 |
|---|---|
First | 첫 번째 요소 선택 |
Last | 마지막 요소 선택 |
Single | 요소가 정확히 하나일 때 선택 |
ElementAt | 지정한 인덱스의 요소 선택 |
요소가 없거나 조건을 만족하는 요소가 없을 때 예외가 발생할 수 있다.
FirstOrDefault, LastOrDefault, SingleOrDefault 등은 요소가 없을 때 기본값을 반환한다.
4-5. 범위 선택 연산자
| 연산자 | 역할 |
|---|---|
Take | 앞에서부터 지정한 개수만큼 선택 |
Skip | 앞에서부터 지정한 개수만큼 건너뜀 |
TakeWhile | 조건이 참인 동안 선택 |
SkipWhile | 조건이 참인 동안 건너뜀 |
TakeWhile과 SkipWhile은 조건이 처음으로 거짓이 되는 지점에서 동작이 달라진다.
4-6. 조건 확인 연산자
| 연산자 | 역할 |
|---|---|
Any | 하나라도 존재하는지 확인 |
All | 모든 요소가 조건을 만족하는지 확인 |
Contains | 특정 값이 포함되어 있는지 확인 |
Any()는 요소가 하나라도 존재하는지 확인할 때 사용한다.
Count() > 0과 달리 하나의 요소만 확인해도 결과를 알 수 있으므로 불필요한 순회를 줄일 수 있다.
빈 시퀀스에 All()을 사용하면 true를 반환한다.
4-7. 집합 연산자
| 연산자 | 역할 |
|---|---|
Distinct | 중복 제거 |
Concat | 두 시퀀스를 연결 |
Union | 합집합 |
Intersect | 교집합 |
Except | 차집합 |
Concat은 중복을 유지하지만 나머지 집합 연산자는 중복을 제거한다.
5. LINQ 쿼리 구문
5-1. 쿼리 구문
쿼리 구문은 LINQ를 SQL과 비슷한 형태로 작성하는 방식이다.
주요 구성 요소
| 구문 | 역할 |
|---|---|
from | 데이터 원본 지정 |
where | 조건 지정 |
orderby | 정렬 기준 지정 |
select | 반환할 결과 지정 |
기본적으로 from으로 시작하고 select로 끝난다.
5-2. 메서드 구문과 쿼리 구문
LINQ는 메서드 구문과 쿼리 구문 두 가지 방식으로 작성할 수 있다.
- 메서드 구문은 LINQ 연산자를 연결하는 방식이다.
- 쿼리 구문은
from,where,orderby,select등을 사용하는 방식이다.
두 방식은 같은 결과를 표현할 수 있으며, 컴파일러가 쿼리 구문을 메서드 구문으로 변환한다.
6. LINQ의 실행 방식
6-1. 지연 실행
대부분의 LINQ 쿼리는 지연 실행 방식으로 동작한다.
쿼리를 작성하는 순간에는 실제 데이터 처리가 이루어지지 않고, foreach 등으로 결과를 요청할 때 실행된다.
6-2. 즉시 실행
즉시 실행은 연산자를 호출하는 시점에 실제 계산을 수행하는 방식이다.
주요 즉시 실행 연산자
- 집계:
Count,Sum,Average,Min,Max - 요소 선택:
First,Last,Single,ElementAt - 조건 확인:
Any,All,Contains - 컬렉션 변환:
ToList,ToArray,ToDictionary
6-3. ToList와 ToArray
ToList()와 ToArray()는 지연 실행 쿼리를 즉시 실행하고 결과를 새로운 컬렉션에 저장한다.
| 메서드 | 반환 자료형 |
|---|---|
ToList() | List<T> |
ToArray() | T[] |
결과를 저장하면 이후 원본 컬렉션의 요소가 추가되거나 삭제되어도 저장된 컬렉션의 요소 구성은 유지된다.
단, 참조 형식의 객체를 저장하는 경우 객체 내부의 변경까지 방지하지는 않는다.
6-4. ToList와 ToArray를 사용하는 경우
- 동일한 쿼리를 여러 번 순회할 때
- 계산 결과를 반복해서 사용해야 할 때
- 특정 시점의 결과를 유지해야 할 때
한 번만 순회하고 원본이 변경되지 않는다면 지연 실행을 그대로 사용하는 것도 적절하다.
7. 전체 개념의 연결
이터레이터와 LINQ는 서로 독립적인 기능이 아니라 연결된 개념이다.
IEnumerable<T>는 데이터를 순회할 수 있도록 한다.IEnumerator<T>는 실제 순회 과정을 담당한다.foreach는 열거 규약을 이용해 데이터를 순회한다.yield return은 이터레이터를 통해 데이터를 하나씩 제공한다.- LINQ는 시퀀스를 대상으로 데이터를 검색하고 변환한다.
- LINQ의 여러 연산자는 지연 실행을 통해 필요한 시점에 데이터를 처리한다.
ToList()와ToArray()는 결과를 즉시 계산해 컬렉션에 저장한다.



