을 (를) 사용하고 yield return
있습니다. 그렇게 할 때 컴파일러는 상태 머신을 구현하는 생성 된 클래스를 반환하는 함수로 메서드를 다시 작성합니다.
광범위하게 말하면 해당 클래스의 필드에 로컬을 다시 작성하고 yield return
명령어 사이의 알고리즘의 각 부분 이 상태가됩니다. 컴파일 후이 메서드가 어떻게되는지 디 컴파일러를 통해 확인할 수 있습니다 (를 생성하는 스마트 디 컴파일을 해제해야합니다 yield return
).
그러나 결론은 반복을 시작할 때까지 메서드의 코드가 실행되지 않는다는 것입니다.
전제 조건을 확인하는 일반적인 방법은 방법을 두 가지로 분할하는 것입니다.
public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
if (str == null)
throw new ArgumentNullException("str");
if (searchText == null)
throw new ArgumentNullException("searchText");
return AllIndexesOfCore(str, searchText);
}
private static IEnumerable<int> AllIndexesOfCore(string str, string searchText)
{
for (int index = 0; ; index += searchText.Length)
{
index = str.IndexOf(searchText, index);
if (index == -1)
break;
yield return index;
}
}
이것은 첫 번째 메서드가 예상 한대로 (즉시 실행) 동작하고 두 번째 메서드로 구현 된 상태 머신을 반환하기 때문에 작동합니다.
확장 메서드 는 구문 설탕 일 뿐이므로 값에 대해 호출 할 수 있으므로에 str
대한 매개 변수 도 확인해야합니다 .null
null
컴파일러가 코드에 대해 수행하는 작업에 대해 궁금한 경우 컴파일러 생성 코드 표시 옵션을 사용하여 dotPeek로 디 컴파일 된 메서드는 다음과 같습니다 .
public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
Test.<AllIndexesOf>d__0 allIndexesOfD0 = new Test.<AllIndexesOf>d__0(-2);
allIndexesOfD0.<>3__str = str;
allIndexesOfD0.<>3__searchText = searchText;
return (IEnumerable<int>) allIndexesOfD0;
}
[CompilerGenerated]
private sealed class <AllIndexesOf>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{
private int <>2__current;
private int <>1__state;
private int <>l__initialThreadId;
public string str;
public string <>3__str;
public string searchText;
public string <>3__searchText;
public int <index>5__1;
int IEnumerator<int>.Current
{
[DebuggerHidden] get
{
return this.<>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden] get
{
return (object) this.<>2__current;
}
}
[DebuggerHidden]
public <AllIndexesOf>d__0(int <>1__state)
{
base..ctor();
this.<>1__state = param0;
this.<>l__initialThreadId = Environment.CurrentManagedThreadId;
}
[DebuggerHidden]
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
Test.<AllIndexesOf>d__0 allIndexesOfD0;
if (Environment.CurrentManagedThreadId == this.<>l__initialThreadId && this.<>1__state == -2)
{
this.<>1__state = 0;
allIndexesOfD0 = this;
}
else
allIndexesOfD0 = new Test.<AllIndexesOf>d__0(0);
allIndexesOfD0.str = this.<>3__str;
allIndexesOfD0.searchText = this.<>3__searchText;
return (IEnumerator<int>) allIndexesOfD0;
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
}
bool IEnumerator.MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
if (this.searchText == null)
throw new ArgumentNullException("searchText");
this.<index>5__1 = 0;
break;
case 1:
this.<>1__state = -1;
this.<index>5__1 += this.searchText.Length;
break;
default:
return false;
}
this.<index>5__1 = this.str.IndexOf(this.searchText, this.<index>5__1);
if (this.<index>5__1 != -1)
{
this.<>2__current = this.<index>5__1;
this.<>1__state = 1;
return true;
}
goto default;
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
void IDisposable.Dispose()
{
}
}
이것은 잘못된 C # 코드입니다. 컴파일러는 언어가 허용하지 않지만 IL에서는 합법적입니다. 예를 들어 이름 충돌을 피할 수없는 방식으로 변수 이름을 지정합니다.
그러나 보시 AllIndexesOf
다시피 유일한 생성자는 객체를 생성하고 반환하며 생성자는 일부 상태 만 초기화합니다. GetEnumerator
개체 만 복사합니다. 실제 작업은 열거를 시작할 때 수행됩니다 ( MoveNext
메서드 호출 ).