2022. 3. 25. 15:24ㆍLanguages/C#
foreach
foreach 문은 for 문처럼 인덱스 변수가 필요 없다. 배열이나 리스트같은 컬렉션에서만 사용이 가능하다.
foreach 문이 객체 내의 요소를 순회하기 위해서는 foreach 문과의 약속을 지켜야만 한다.
그 약속은 IEnumerable 인터페이스를 상속하는 클래스 구현이다.
즉, 어떤 클래스라도 IEnumerable 인터페이스를 상속하기만 하면 foreach 문을 사용할 수 있다는 얘기가 된다.
IEnumerable 인터페이스
인터페이스를 상속받는 클래스는 반드시 인터페이스의 모든 내용을 구현해야 한다는 것은 기억할 것이다.
IEnumerable 인터페이스가 갖고 있는 메소드는 단 하나 뿐이며, 상속받는 클래스는 다음 메소드를 구현해야 한다.
IEumerator GetEnumerator(); // IEnumerator 형식의 객체를 반환
GetEnumerator() 메소드는IEnumerator 인터페이스를 상속받는 클래스의 객체를 반환해야 한다.
직접 IEnumerator 인터페이스를 상속받는 클래스를 구현해도 되고, 컴파일러가 지원하는 yield 문을 이용해도 된다.
먼저, yield 문을 사용한 방법을 알아보자.
yield 문
yield 문을 사용하면 IEnumerator 인터페이스를 상속받는 클래스를 따로 구현하지 않아도, 컴파일러가 자동으로 해당 인터페이스를 구현한 클래스를 생성해준다.
yield return
현재 메소드(GetEnumerator())의 실행을 일시 정지하고, 호출자에게 결과를 반환한다.
메소드가 다시 호출되면, 일시 정지된 실행을 복구하여 yield return 또는 yield break 문을 만날 때까지 작업을 계속한다.
using System;
using System.Collections;
class MyEnumerator
{
private int[] numbers = { 1, 2, 3, 4 };
public IEnumerator GetEnumerator()
{
yield return numbers[0];
yield return numbers[1];
yield return numbers[2];
yield break; // yield break는 GetEnumerator() 메소드를 종료시킴
yield return numbers[3]; // 따라서 이 부분은 실행되지 않음
}
}
테스트를 위해 실행해보면 다음과 같은 결과가 나온다.
var _object = new MyEnumerator();
foreach(int i in _object)
{
Console.WriteLine(i);
}
yield 문을 사용하면 IEnumerator 인터페이스를 상속받는 클래스를 별도로 구현할 필요없이, 이렇게 컴파일러가 자동적처리해준다. 그렇다면, 직접 IEnumerator 인터페이스를 상속받는 클래스를 만들어본다면 어떨까?
IEnumerator 인터페이스
interface IEnumerator
{
boolean MoveNext();
void Reset();
Object Current { get;}
}
- MoveNext() : 다음 요소로 이동. 컬렉션의 끝을 지난 경우에는 False, 이동이 성공한 경우에는 True 리턴
- Current : 컬렉션의 현재 요소 리턴
- Reset() : 컬렉션의 첫 번째 위치 "앞"으로 이동
- 첫 번째 위치가 0번인 경우, Reset() 호출 시 -1 번으로 이동.
- 첫 번째 위치로의 이동은 MoveNext()를 호출한 다음에 이루어짐
예제 코드
using System;
using System.Collections;
namespace Enumerable
{
class MyList : IEnumerable, IEnumerator
{
private int[] array;
int position = -1; // 컬렉션의 현재 위치를 다루는 변수
public MyList()
{
array = new int[3];
}
public int this[int index] // 인덱서
{
get { return array[index]; }
set
{
if (index >= array.Length)
{
Array.Resize<int>(ref array, index + 1);
Console.WriteLine($"Array Resized : {array.Length}");
}
array[index] = value;
}
}
// IEnumerator 멤버
public object Current
{
get { return array[position]; }
}
// IEnumerator 멤버
public bool MoveNext()
{
if (position == array.Length - 1)
{
Reset();
return false;
}
position++;
return (position < array.Length);
}
// IEnumerator 멤버
public void Reset()
{
position = -1;
}
// IEnumerable 멤버
public IEnumerator GetEnumerator()
{
return this;
}
}
}
그리고 테스트를 위해 돌려보면 다음과 같이 나온다.
MyList list = new MyList();
for(int i = 0; i < 5; i++)
{
list[i] = i;
}
foreach(int i in list)
{
Console.WriteLine(i);
}
- 이 글은 <이것이 C#이다> 책을 바탕으로 공부한 글입니다.
'Languages > C#' 카테고리의 다른 글
[C#] 콜백 함수(Callback)는 무엇이고 대리자(Delegate)는 뭘까? (0) | 2022.03.27 |
---|---|
[C#] 프로그램이 어떠한 상황에서도 잘 견딜 수 있도록! : 예외 처리(Exception Handling) (0) | 2022.03.26 |
[C#] 객체를 배열처럼 인덱싱하자! : 인덱서(Indexer) (0) | 2022.03.25 |
[C#] 레코드(Record) 형식으로 만드는 불변(Immutable) 객체 (1) | 2022.03.23 |
[C#] 인터페이스와 클래스의 사이 : 추상 클래스(Abstract Class) (0) | 2022.03.23 |