Stai usando yield return
. Quando lo fai, il compilatore riscriverà il tuo metodo in una funzione che restituisce una classe generata che implementa una macchina a stati.
In generale, riscrive i locali nei campi di quella classe e ogni parte del tuo algoritmo tra le yield return
istruzioni diventa uno stato. Puoi controllare con un decompilatore cosa diventa questo metodo dopo la compilazione (assicurati di disattivare la decompilazione intelligente che produrrebbe yield return
).
Ma la linea di fondo è: il codice del tuo metodo non verrà eseguito finché non inizi a iterare.
Il modo usuale per verificare le precondizioni è dividere il metodo in due:
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;
}
}
Questo funziona perché il primo metodo si comporterà proprio come ci si aspetta (esecuzione immediata) e restituirà la macchina a stati implementata dal secondo metodo.
Nota che dovresti anche controllare il str
parametro null
, perché i metodi di estensione possono essere chiamati sui null
valori, poiché sono solo zucchero sintattico.
Se sei curioso di sapere cosa fa il compilatore al tuo codice, ecco il tuo metodo, decompilato con dotPeek usando l' opzione Mostra codice generato dal compilatore .
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()
{
}
}
Questo non è un codice C # non valido, perché il compilatore è autorizzato a fare cose che il linguaggio non consente, ma che sono legali in IL, ad esempio denominare le variabili in un modo che non è possibile evitare conflitti di nomi.
Ma come puoi vedere, the AllIndexesOf
only costruisce e restituisce un oggetto, il cui costruttore inizializza solo uno stato. GetEnumerator
copia solo l'oggetto. Il vero lavoro viene fatto quando inizi a enumerare (chiamando il MoveNext
metodo).