Vous utilisez yield return
. Ce faisant, le compilateur réécrira votre méthode dans une fonction qui renvoie une classe générée qui implémente une machine à états.
En gros, il réécrit les sections locales dans les champs de cette classe et chaque partie de votre algorithme entre les yield return
instructions devient un état. Vous pouvez vérifier avec un décompilateur ce que devient cette méthode après la compilation (assurez-vous de désactiver la décompilation intelligente qui produirait yield return
).
Mais l'essentiel est que le code de votre méthode ne sera pas exécuté tant que vous ne commencerez pas l'itération.
La manière habituelle de vérifier les conditions préalables est de diviser votre méthode en deux:
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;
}
}
Cela fonctionne car la première méthode se comportera exactement comme vous le souhaitez (exécution immédiate) et renverra la machine à états implémentée par la deuxième méthode.
Notez que vous devez également vérifier le str
paramètre null
, car les méthodes d'extensions peuvent être appelées sur des null
valeurs, car elles ne sont que du sucre syntaxique.
Si vous êtes curieux de savoir ce que le compilateur fait à votre code, voici votre méthode, décompilée avec dotPeek à l'aide de l' option Afficher le code généré par le compilateur .
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()
{
}
}
Ce code C # n'est pas valide, car le compilateur est autorisé à faire des choses que le langage n'autorise pas, mais qui sont légales en IL - par exemple en nommant les variables d'une manière que vous ne pourriez pas éviter les collisions de noms.
Mais comme vous pouvez le voir, le AllIndexesOf
seul construit et renvoie un objet, dont le constructeur n'initialise que certains états. GetEnumerator
copie uniquement l'objet. Le vrai travail est fait lorsque vous commencez à énumérer (en appelant la MoveNext
méthode).