Вы используете 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 - например, именовать переменные таким образом, чтобы избежать конфликтов имен.
Но, как видите, AllIndexesOfonly создает и возвращает объект, конструктор которого только инициализирует некоторое состояние. GetEnumeratorтолько копирует объект. Настоящая работа выполняется, когда вы начинаете перечисление (путем вызова MoveNextметода).