Ви використовуєте 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, використовуючи параметр Show Code-generated Code, створений компілятором .
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 - наприклад, називати змінні таким чином, щоб ви не могли уникнути зіткнень імен.
Але, як бачите, AllIndexesOf
єдиний конструює та повертає об’єкт, конструктор якого лише ініціалізує деякий стан. GetEnumerator
лише копіює об’єкт. Справжня робота виконується, коли ви починаєте перерахувати (викликаючи MoveNext
метод).