Estás usando yield return
. Al hacerlo, el compilador reescribirá su método en una función que devuelve una clase generada que implementa una máquina de estado.
En términos generales, reescribe locales en campos de esa clase y cada parte de su algoritmo entre las yield return
instrucciones se convierte en un estado. Puede verificar con un descompilador en qué se convierte este método después de la compilación (asegúrese de desactivar la descompilación inteligente que produciría yield return
).
Pero la conclusión es: el código de su método no se ejecutará hasta que comience a iterar.
La forma habitual de comprobar las condiciones previas es dividir su método en dos:
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;
}
}
Esto funciona porque el primer método se comportará tal como lo espera (ejecución inmediata) y devolverá la máquina de estado implementada por el segundo método.
Tenga en cuenta que también debe verificar el str
parámetro null
, porque los métodos de extensión se pueden llamar en null
valores, ya que son simplemente azúcar sintáctico.
Si tiene curiosidad acerca de lo que hace el compilador con su código, aquí está su método, descompilado con dotPeek usando la opción Mostrar código generado por el compilador .
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()
{
}
}
Este es un código C # inválido, porque el compilador puede hacer cosas que el lenguaje no permite, pero que son legales en IL, por ejemplo, nombrar las variables de una manera que no podría evitar las colisiones de nombres.
Pero como puede ver, el AllIndexesOf
único construye y devuelve un objeto, cuyo constructor solo inicializa algún estado. GetEnumerator
solo copia el objeto. El trabajo real se realiza cuando comienzas a enumerar (llamando al MoveNext
método).