Bạn đang sử dụng yield return
. Khi làm như vậy, trình biên dịch sẽ viết lại phương thức của bạn thành một hàm trả về một lớp được tạo để triển khai một máy trạng thái.
Nói chung, nó ghi lại các local vào các trường của lớp đó và mỗi phần trong thuật toán của bạn giữa các yield return
lệnh sẽ trở thành một trạng thái. Bạn có thể kiểm tra bằng trình dịch ngược phương thức này sẽ trở thành gì sau khi biên dịch (đảm bảo tắt tính năng dịch ngược thông minh sẽ tạo ra yield return
).
Nhưng điểm mấu chốt là: mã phương thức của bạn sẽ không được thực thi cho đến khi bạn bắt đầu lặp lại.
Cách thông thường để kiểm tra các điều kiện tiên quyết là chia phương pháp của bạn thành hai:
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;
}
}
Điều này hoạt động vì phương thức đầu tiên sẽ hoạt động giống như bạn mong đợi (thực thi ngay lập tức) và sẽ trả về máy trạng thái được thực hiện bởi phương thức thứ hai.
Lưu ý rằng bạn cũng nên kiểm tra str
tham số null
vì các phương thức mở rộng có thể được gọi trên null
các giá trị, vì chúng chỉ là đường cú pháp.
Nếu bạn tò mò về những gì trình biên dịch làm với mã của bạn, đây là phương pháp của bạn, được dịch ngược với dotPeek bằng cách sử dụng tùy chọn Hiển thị mã do trình biên dịch tạo .
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()
{
}
}
Đây là mã C # không hợp lệ, vì trình biên dịch được phép thực hiện những điều mà ngôn ngữ không cho phép, nhưng lại hợp pháp trong IL - ví dụ: đặt tên cho các biến theo cách bạn không thể tránh xung đột tên.
Nhưng như bạn có thể thấy, hàm AllIndexesOf
duy nhất tạo và trả về một đối tượng, mà hàm tạo chỉ khởi tạo một số trạng thái. GetEnumerator
chỉ sao chép đối tượng. Công việc thực sự được thực hiện khi bạn bắt đầu liệt kê (bằng cách gọi MoveNext
phương thức).