※ 일단 "yield"는 "항복"보다는 "produce"라는 의미로 해석하자.
C# 2.0에서 봤던 것 같은데, "yield return", 이런 녀석이 있구나 하고 그냥 넘어갔었다. 이 녀석을 다시 보게 된 것은 LINQ 때문이다. 그때 정리 좀 해야겠다 싶었던 LINQ의 "Deferred Execution"특성이 "yield return"과 연관되어 있다는 것을 느꼈다. yield return을 이해하면 도움이 될 것 같다는 생각을 했다.
■ yield return 이란 뭣인가?
지금까지 봐온 봐로는 yield return은 주로 컬렉션의 iterator를 구현할 때 이용하는 듯 하다. 다음과 같이 컬렉션이 있다고 해 보자.
private static readonly string[] StringValues = new string[] { "The", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog" }; |
이 컬렉션을 다음처럼 루핑하는 iterator를 만들고자 한다.
foreach(string v in TestIterator()) { Console.WriteLine("In foreach:{0}", v); } |
그럼 다음과 같은 iterator를 만들면 된다.
static IEnumerable<string> TestIterator() { foreach(string value in StringValues) { // 이곳에서 필요하면 value에 다른 가공할 수도 있다.
yield return value; } } |
yield return이 값을 반환하는 것까지는 일반 메소드와 다를 것이 없다. 그러나 C#에는 yield return을 만나면 "마지막 값을 반환한 위치(상태)를 기억"할 수 있는 메커니즘이 있다는 것이다. 위치를 기억해뒀다가 다음에 요청을 받으면 그때(on demand) 다음 요소를 반환해준다. 여기서 중요한 것은 요소에 대한 요청을 받으면 그때 반환해주는 특성이다.
iterator의 반환값은 단순한 스칼라 값이 아니라 IEnuerable<T>이라는 것을 기억하자. 값을 반환하고 끝나는 것이 아니라는 점에서 return과 다르다. 다음 요소에 대한 요청을 받으면 그때 반환해 줄 요소에 대한 위치를 기억하고 있다는 것이다.
이런 특성으로 yield return은 위 예처럼 주로 foreach-ing에 사용된다.
이처럼 클라이언트 코드에서 실제로 요청할때 값에 접근하는 것을 deferred execution이라고 한다. IEnueraable<T> 객체를 반환할때 컬렉션 객체 자체를 반환하는 것이 아니라, 컬렉션 생성기(Collection generator)를 반환하고 있는 것이다.
■ 이런 기법은 왜 사용할까?
1) yield return의 장점은 컬렉션을 생성할 필요가 없다는 것이다. 때로 컬렉션을 생성하는 하는 작업은 비용이 많이 드는 경우가 있다. 클라이언트 코드가 요청할 때 요청하는 요소만 반환해도 되는 경우가 있다.
yield return은 데이터 구조를 순회(traverse)하는 경우 다양한 방법을 제공한다. 만약 트리 구조라면, 리스트 객체를 만들지 않고도 pre- 또는 post 순으로 순회할 수 있다.
public IEnumerable<T> InOrder() { foreach (T k in kids) foreach (T n in k.InOrder()) yield return n; yield return (T) this; } public IEnumerable<T> PreOrder() { yield return (T) this; foreach (T k in kids) foreach (T n in k.PreOrder()) yield return n; } |
2) 지연 시퀀스(lazy sequence)이란 것이 있는데, 이것은 yield return의 "on demand" 속성을 이용한다. 지연 시퀀스는 많은 장점이 있다.
- 리소스가 많이 필요한 연산을 필요할 때까지 미룰 수 있다.
- 메모리 용량을 초과하는 시퀀스를 다룰 수 있다.
- I/O도 지연 시킬 수 있다.
이런 컨셉은 "함수적 프로그래밍(functional programming)"에서 사용된다.
LINQ도 "함수적 프로그래밍" 특성이 있다. 아래 링크에 가 보면 다음과 같은 샘플 코드가 있다.
public void DeferredExecution() { // Create an input sequence var lstNumbers = new List |
select 같은 함수는 yield return를 사용하는 함수들이다. 실제로 요소에 대한 값을 요청할 때까지 값을 반환하지 않는다.
public static IEnumerable |
그래서 PrintCollection(lst10Times);에서 "lst10Times"를 통해 요소의 값을 요청할때까지 값을 반환하지 않다. 그럼 lst10Times 변수의 값들을 출력하기 전에 lstNumbers.Add(4); 처럼 lstNumbers에 요소를 삽입하면 어떻게 될까? 결국 PrintCollection(lst10Times);에서는 4도 출력된다는 것이다.
3) on demand 특성은 LINQ의 결과로 나온 컬렉션 변수를 원격으로 반환해서는 안된다는 의미이다. 즉 우리가 익숙한 layered style의 아키텍처에서 데이터베이스의 결과를 LINQ한 결과인 IEnumerable<T>를 클라이언트 단에 반환해 줄 수 없다는 의미이다.
■ 참조
What is the purpose/advantage of using yield return iterators in C#?
http://stackoverflow.com/questions/1088442/what-is-the-purpose-advantage-of-using-yield-return-iterators-in-c
A closer look at yield – part 2
http://blogs.msdn.com/b/stuartleeks/archive/2008/07/15/a-closer-look-at-yield-part-2.aspx
LINQ
http://www.diranieh.com/NETCSharp/LINQ.htm
Iterator block implementation details: auto-generated state machines
http://blogs.msdn.com/b/stuartleeks/archive/2008/07/15/a-closer-look-at-yield-part-2.aspx
'IT 살이 > 04. 기술 - 프로그래밍' 카테고리의 다른 글
ASP.NET 웹 어플리케이션 인스턴스와 HttpModule (0) | 2016.01.28 |
---|---|
log4net 정리 & 예제 (1) | 2016.01.19 |
ASP.NET Session ID 특성 (0) | 2016.01.13 |