C# - yield 문을 사용할 수 있는 메서드의 조건
다음과 같은 질문이 있군요. ^^
yield return의 리턴 타입 질문드립니다.
; https://www.sysnet.pe.kr/3/0/5156
우선, yield 문을 사용할 수 있는 메서드 조건은 문서에 잘 나와 있습니다.
yield (C# Reference)
; https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/yield
- 반환 타입은 반드시 IEnumerable, IEnumerable<T>, IEnumerator, IEnumerator<T> 중의 하나여야 한다.
- 메서드의 인자에는 in, ref, out 조건을 가질 수 없다.
첫 번째 조건에 의해, C# 컴파일러는 yield 문이 사용된 메서드의 반환 형식이 IEnumerable이면 자동 생성되는 클래스를 IEnumerable과 IEnumerator를 함께 상속받도록 만듭니다. 반면 IEnumerator를 반환하는 메서드의 경우 IEnumerable은 필요 없으니 제외하고 IEnumerator만 구현합니다.
직접 확인해 볼까요? ^^ 본문(
yield return의 리턴 타입 질문드립니다.)에 실린 예제를 보면,
using System;
using System.Collections;
using System.Collections.Generic;
public class NameSet : IEnumerable
{
private List<string> names = new List<string>();
public NameSet(params string[] values)
{
foreach (var e in values)
this.names.Add(e);
}
public IEnumerable NameList()
{
int currentIndex = 0;
while (currentIndex < names.Count)
{
yield return names[currentIndex];
currentIndex++;
}
}
public IEnumerator GetEnumerator()
{
int currentIndex = 0;
while (currentIndex < names.Count)
{
yield return names[currentIndex];
currentIndex++;
}
}
}
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var names = new NameSet("abc", "def", "ghi");
foreach (var e in names.NameList())
Console.WriteLine(e);
foreach (var e in names)
Console.WriteLine(e);
}
}
}
NameList 메서드의 경우 IEnumerable을 반환하므로 C# 컴파일러는 다음과 같은 식의 클래스를 생성해 줍니다.
[CompilerGenerated]
private sealed class <NameList>d__2 : IEnumerable<object>, IEnumerable, IEnumerator<object>, IDisposable, IEnumerator
{
// Fields
private int <>1__state;
private object <>2__current;
public NameSet <>4__this;
private int <>l__initialThreadId;
private int <currentIndex>5__1;
// Methods
[DebuggerHidden]
public <NameList>d__2(int <>1__state);
private bool MoveNext();
[DebuggerHidden]
IEnumerator<object> IEnumerable<object>.GetEnumerator();
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator();
[DebuggerHidden]
void IEnumerator.Reset();
[DebuggerHidden]
void IDisposable.Dispose();
// Properties
object IEnumerator<object>.Current { [DebuggerHidden] get; }
object IEnumerator.Current { [DebuggerHidden] get; }
}
달리 말하면, NameList 메서드는 IEnumerable + IEnumerator가 구현된 클래스로 바뀌는 것입니다. 반면 IEnumerator를 반환하는 GetEnumerator 메서드는,
[CompilerGenerated]
private sealed class <GetEnumerator>d__3 : IEnumerator<object>, IDisposable, IEnumerator
{
// Fields
private int <>1__state;
private object <>2__current;
public NameSet <>4__this;
private int <currentIndex>5__1;
// Methods
[DebuggerHidden]
public <GetEnumerator>d__3(int <>1__state);
private bool MoveNext();
[DebuggerHidden]
void IEnumerator.Reset();
[DebuggerHidden]
void IDisposable.Dispose();
// Properties
object IEnumerator<object>.Current { [DebuggerHidden] get; }
object IEnumerator.Current { [DebuggerHidden] get; }
}
간단하게 IEnumerator만을 구현한 클래스로 바뀌는 것을 볼 수 있습니다.
참고로, foreach 구문은 IEnumerator/IEnumerable 인터페이스를 구현하지 않아도 되므로,
47. foreach 문을 사용하려면 IEnumerator/IEnumerable 인터페이스를 반드시 구현해야 할까?
; https://www.sysnet.pe.kr/2/0/11876#tag47
NameSet의 IEnumerable 상속은 (foreach에서만 사용한다면) 제거해도 무방합니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]