Używasz yield return
. Robiąc to, kompilator przepisuje twoją metodę do funkcji, która zwraca wygenerowaną klasę, która implementuje maszynę stanów.
Mówiąc ogólnie, przepisuje lokalne zmienne do pól tej klasy i każda część algorytmu między yield return
instrukcjami staje się stanem. Możesz sprawdzić za pomocą dekompilatora, czym ta metoda staje się po kompilacji (upewnij się, że wyłączono inteligentną dekompilację, która przyniosłaby efekt yield return
).
Ale najważniejsze jest to, że kod twojej metody nie zostanie wykonany, dopóki nie zaczniesz iteracji.
Zwykłym sposobem sprawdzenia warunków wstępnych jest podzielenie metody na dwie części:
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;
}
}
To działa, ponieważ pierwsza metoda będzie zachowywać się tak, jak się spodziewasz (natychmiastowe wykonanie) i zwróci maszynę stanu zaimplementowaną przez drugą metodę.
Zauważ, że powinieneś również sprawdzić str
parametr for null
, ponieważ metody rozszerzeń mogą być wywoływane na null
wartościach, ponieważ są one po prostu cukrem składniowym.
Jeśli jesteś ciekawy, co kompilator robi z twoim kodem, oto twoja metoda, zdekompilowana za pomocą dotPeek przy użyciu opcji Pokaż kod wygenerowany przez kompilator .
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()
{
}
}
Jest to nieprawidłowy kod C #, ponieważ kompilator może robić rzeczy, których język nie zezwala, ale które są legalne w IL - na przykład nazwanie zmiennych w sposób, w jaki nie można uniknąć kolizji nazw.
Ale jak widać, AllIndexesOf
jedyny konstruuje i zwraca obiekt, którego konstruktor inicjuje tylko pewien stan. GetEnumerator
kopiuje tylko obiekt. Prawdziwa praca jest wykonywana, gdy zaczynasz wyliczać (przez wywołanie MoveNext
metody).