C#을 만지다보면 종종 등장하던 IEnumerable
.
인덱서를 이용해 인덱스를 오버라이딩할 줄은 알았는데,
foreach
는 오버라이딩이 안되나? 라는 궁금증을 가지고 있었는데,
그 정답이 IEnumerable
이었다.
이 김에 정리해보자.
Enumerable vs IEnumerable?
들어가기 전에 확실히 할 부분이,
System.Linq
의 Enumerable
클래스에 대해 이야기 하는 것이 아니다.
Enumerable
클래스는
열거형(Enumerable) 컬렉션에 사용가능한 메서드를 모아놓은 클래스라고 생각하면 된다.
즉, 기존의 열거형 컨테이너였던
Array
나 List
는 물론이거니와,
IEnumerable<T>
를 구현해놓은 모든 object에 접목하여 사용할 수 있다.
직접 열거형에 대한 함수를 구현할 일은 잘 없고, 내가 만든 클래스를 열거형으로 만드는 경우가 많기 때문에
IEnumerable
인터페이스를 다룰 일이 훨씬 많을 것이다.
IEnumerable 기본 구조
IEnumerable
의 코드는 이렇게 되어 있다.
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
너무 단순해서 당황했다.
알고보니 생각한 기능들은 전부 IEnumerator
(열거자)에 있었다.
public interface IEnumerator
{
object? Current { get; }
bool MoveNext();
void Reset();
}
Current
는 현재 위치의 개체를 반환한다.
MoveNext
는 다음 인덱스가 유효한지 아닌지를 반환한다. 유효할 경우 다음 위치로 이동한다.
Reset
은 Enumerator를 초기 위치, 즉 처음으로 다시 돌린다.
IEnumerable 상속 예시
using System;
using System.Collections;
// Simple business object.
public class Person
{
public Person(string fName, string lName)
{
this.firstName = fName;
this.lastName = lName;
}
public string firstName;
public string lastName;
}
// Collection of Person objects. This class
// implements IEnumerable so that it can be used
// with ForEach syntax.
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
// Implementation for the GetEnumerator method.
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) GetEnumerator();
}
public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
}
PeopleEnum이라는 Enumerator는 바로 다음에 있다.
원래 IEnumerable
은 명시적 구현이 되어 있기 때문에
이를 외부에서 호출하려면 객체를 인터페이스 타입으로 업캐스팅해서 호출해줘야 한다.
IEnumerable
의 GetEnumerator()
는 IEnumerator
를 반환해줘야하니
PeopleEnum
을 IEnumerator
로 업캐스팅해서 반환한다.
명시적 암시적에 관한 내용은 다음의 좋은 글이 있으니 참고하자.
// When you implement IEnumerable, you must also implement IEnumerator.
public class PeopleEnum : IEnumerator
{
public Person[] _people;
// Enumerators are positioned before the first element
// until the first MoveNext() call.
int position = -1;
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
IEnumerator
를 상속받는 부분에서는
Current
, MoveNext
, Reset
에 대한 부분을 모두 구현해줘야 한다.
foreach
IEnumerable
을 상속받은 객체는
foreach
로 순회가 가능하다!
IEnumerable
을 쓰는 가장 큰 이유가 아닐까.
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);
yield
사실 IEnumerable
상속을 매우 간단하게 구현할 수 있다.
바로 yield
이다.
yield
는 비동기 방식의 함수 호출을 위해 사용하는 문으로,
함수 호출자(Caller)에게 컬렉션의 요소를 하나씩 반환할 때 사용한다.
yield return
을 하게 되면
함수의 실행이 완전 끝나는 것이 아니라
그 함수를 다시 실행하게 되면
마지막으로 return했던 장소로 돌아와 다시 함수 구문을 실행한다.
foreach (int i in ProduceEvenNumbers(9))
{
Console.Write(i);
Console.Write(" ");
}
// Output: 0 2 4 6 8
IEnumerable<int> ProduceEvenNumbers(int upto)
{
for (int i = 0; i <= upto; i += 2)
{
yield return i;
}
}
반복문과 같이 사용하기 때문에 반복 종료를 명시적으로 해줄 수도 있다.
바로 yield break
이다.
Console.WriteLine(string.Join(" ", TakeWhilePositive(new[] { 2, 3, 4, 5, -1, 3, 4})));
// Output: 2 3 4 5
Console.WriteLine(string.Join(" ", TakeWhilePositive(new[] { 9, 8, 7 })));
// Output: 9 8 7
IEnumerable<int> TakeWhilePositive(IEnumerable<int> numbers)
{
foreach (int n in numbers)
{
if (n > 0)
{
yield return n;
}
else
{
yield break;
}
}
}
yield return이 작동할 때, 처리를 위해
컴파일러는 자동으로 IEnumerable
과 IEnumerator
클래스를 자동으로 생성한다!
이 때문에 yield를 사용하면 따로 IEnumerator를 구현할 필요가 없는 것이다.
yield의 강점
yield
가 좋은 점은 컬렉션의 요소를 일시에 return하지 않는다는 점에서 온다.
따라서 해당 상황에 강점을 가진다.
- 컬렉션의 데이터 양이 매우 큰 경우
- 메서드가 무제한의 데이터를 반환할 경우
- 데이터 하나하나를 계산하는데 걸리는 시간이 너무 오래걸리는 경우
Uploaded by N2T
'개발 > Unity 내일배움캠프 TIL' 카테고리의 다른 글
C# 패턴 일치 (Pattern Matching) (0) | 2023.09.01 |
---|---|
C# 중첩 클래스와 partial 클래스 (0) | 2023.08.31 |
C# Action으로 종속성 없애기..? (0) | 2023.08.29 |
C# StringBuilder 정리 (0) | 2023.08.28 |
C# LINQ 간단 정리 (0) | 2023.08.25 |