在我的代码中,我有一个像这样的方法
public static IEnumerable<int> GetDiff(int start, int end)
{
while (start < end)
{
yield return start;
start++;
}
yield break; // do we need to call it explicitly?
}
因此,我感兴趣的测试用例是GetDiff(1, 5)
和GetDiff(5, 1)
。虽然很清楚在第一种情况下会发生什么,但还不清楚在没有yield break;
循环之后的第二种情况下如何完成
答案 0 :(得分:7)
否,这不是必需的。它将起作用:
||
在这种情况下,仅通过退出该函数即可结束该函数的执行。
但是您可以这样写:
return
答案 1 :(得分:2)
将您的代码放入编译器中,进行构建,然后反向工程回C#,结果如下:
using System.Collections.Generic;
public static IEnumerable<int> GetDiff(int start, int end)
{
while (start < end)
{
yield return start;
start++;
}
}
我将LINQPad 5和ILSpy用于LINQPad。
那里没有产量中断。好吧,好吧,那仍然有糖分……让我们翻译成C#1.0。代码是这样的:
using System.Collections.Generic;
using System.Runtime.CompilerServices;
[IteratorStateMachine(typeof(<GetDiff>d__1))]
public static IEnumerable<int> GetDiff(int start, int end)
{
<GetDiff>d__1 <GetDiff>d__ = new <GetDiff>d__1(-2);
<GetDiff>d__.<>3__start = start;
<GetDiff>d__.<>3__end = end;
return <GetDiff>d__;
}
它正在创建一个隐藏的匿名类<GetDiff>d__1
的实例,设置其start
和end
属性并返回它。我们将回到传递给其构造函数的-2
。
以下是与上面相同的代码,除了在IL中:
.method public hidebysig static
class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> GetDiff (
int32 start,
int32 end
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.IteratorStateMachineAttribute::.ctor(class [mscorlib]System.Type) = (
01 00 17 55 73 65 72 51 75 65 72 79 2b 3c 47 65
74 44 69 66 66 3e 64 5f 5f 31 00 00
)
// Method begins at RVA 0x2052
// Code size 22 (0x16)
.maxstack 8
IL_0000: ldc.i4.s -2
IL_0002: newobj instance void UserQuery/'<GetDiff>d__1'::.ctor(int32)
IL_0007: dup
IL_0008: ldarg.0
IL_0009: stfld int32 UserQuery/'<GetDiff>d__1'::'<>3__start'
IL_000e: dup
IL_000f: ldarg.1
IL_0010: stfld int32 UserQuery/'<GetDiff>d__1'::'<>3__end'
IL_0015: ret
} // end of method UserQuery::GetDiff
类<GetDiff>d__1
看起来像这样:
// <GetDiff>d__1
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
[CompilerGenerated]
private sealed class <GetDiff>d__1 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IDisposable, IEnumerator
{
private int <>1__state;
private int <>2__current;
private int <>l__initialThreadId;
private int start;
public int <>3__start;
private int end;
public int <>3__end;
int IEnumerator<int>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <GetDiff>d__1(int <>1__state)
{
this.<>1__state = <>1__state;
<>l__initialThreadId = Environment.CurrentManagedThreadId;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
break;
case 1:
<>1__state = -1;
start++;
break;
}
if (start < end)
{
<>2__current = start;
<>1__state = 1;
return true;
}
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
[DebuggerHidden]
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
<GetDiff>d__1 <GetDiff>d__;
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
<GetDiff>d__ = this;
}
else
{
<GetDiff>d__ = new <GetDiff>d__1(0);
}
<GetDiff>d__.start = <>3__start;
<GetDiff>d__.end = <>3__end;
return <GetDiff>d__;
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
}
}
让我们从构造函数开始:
public <GetDiff>d__1(int <>1__state)
{
this.<>1__state = <>1__state;
<>l__initialThreadId = Environment.CurrentManagedThreadId;
}
我们正在<>1__state
中存储我们传递的值。请记住,这是一个-2
(new <GetDiff>d__1(-2)
)。我们还将存储调用线程的ID。
客户端foreach
循环要做的第一件事是在重新调整后的GetEnumerator
上调用IEnumerable<int>
。有一些逻辑:
[DebuggerHidden]
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
<GetDiff>d__1 <GetDiff>d__;
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
<GetDiff>d__ = this;
}
else
{
<GetDiff>d__ = new <GetDiff>d__1(0);
}
<GetDiff>d__.start = <>3__start;
<GetDiff>d__.end = <>3__end;
return <GetDiff>d__;
}
它正在检查使用者是否是同一线程,并且状态是否不变(从原始-2
起)(如果成立),它将返回自身。否则,它将返回一个克隆。这意味着,如果GetEnumerator
是从另一个线程调用的,或者在迭代开始后由同一线程调用的,则返回的IEnumerator<int>
从头开始(应该如此)。
还要注意,GetEnumerator
将状态更改为0
。那很重要。
现在,请注意MoveNext
方法。这是一个等效于您的代码的状态机:
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
break;
case 1:
<>1__state = -1;
start++;
break;
}
if (start < end)
{
<>2__current = start;
<>1__state = 1;
return true;
}
return false;
}
在第一次调用时,状态为0
,代码进入switch
,并将状态设置为-1
。
在switch
之后,代码检查是否为start < end
。这是检查是否会进入您的while
循环的检查。如果未输入,则仅返回false
并完成。如果输入,则您yield return start
,因此将start
放入<>2__current
,将状态更改为1
,然后返回true
。自返回true
以来,客户端foreach
读取当前值,执行循环并再次调用MoveNext
...
第二次左右,由于它处于1
状态,因此进入了开关,然后再次将状态更改为-1
,然后执行start++
,这是您的下一行...现在您的while
循环了,这意味着我们必须检查start < end
,这就是在switch
之后执行的操作。如果该条件仍然为true
,它将把start
的新值放入<>2__current
中,更改状态并返回true
。
客户端foreach
循环将继续消耗迭代器,直到条件不再成立为止……然后MoveNext
返回false
,这告诉foreach
迭代器完成,循环结束。
作为参考,以下代码等效于foreach
循环(source):
{
E e = ((C)(x)).GetEnumerator();
try {
while (e.MoveNext()) {
V v = (V)(T)e.Current;
embedded_statement
}
}
finally {
... // Dispose e
}
}
那么yield break;
的作用是什么?在这种情况下,什么都没有。 yield break;
用于指示状态机应结束(MoveNext
返回false
),但是状态机无论如何都将在那里结束,因为它是方法的结束。因此,您只会在yield break;
不在方法末尾时才发现它有用(且有意义)。例如,请参见Stanislav Molchanovsky's answer。
此外,我还认为添加yield break;
不会有助于代码的可维护性或可读性。