В моем коде у меня есть такой метод
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;
после цикла
Нет, это не обязательно. Это будет работать:
public static IEnumerable<int> GetDiff(int start, int end)
{
while (start < end)
{
yield return start;
start++;
}
// yield break; - It is not necessary. It is like 'return' which does not return a value.
}
В этом случае выполнение функции завершится простым выходом из нее.
Но вы можете написать так:
public static IEnumerable<int> GetDiff(int start, int end)
{
while (true)
{
if (start >= end)
yield break;
yield return start;
start++;
}
Console.WriteLine("Finish"); // note that this line will not be executed
}
Выложил ваш код в компилятор, собрал и вернул обратно к С#, это получилось:
using System.Collections.Generic;
public static IEnumerable<int> GetDiff(int start, int end)
{
while (start < end)
{
yield return start;
start++;
}
}
Я использовал LINQPad 5 и ILSpy для LINQPad.
Никакого перерыва здесь. Хорошо, хорошо, у которого все еще есть сахар... давайте переведем на С# 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
в <>1__state
значение, которое мы передали. Помните, что это был -2
(new <GetDiff>d__1(-2)
). Мы также храним идентификатор вызывающего потока.
Первое, что будет делать клиентский цикл 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
с исходным -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
(источник):
{
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;
полезно (и имеет смысл), когда это не в конце метода. Например, см. Ответ Станислава Молчановского.
Кроме того, я бы также сказал, что добавление yield break;
не способствует удобству сопровождения или читабельности кода.
MoveNext = false
для вас, и нам не нужно явно yield break
в конце метода
Здесь вы использовали yield return
и yield break
,
yield return
: возвращает значение в IEnumerable и продолжает со следующего оператора.
yield break
: yield break работает как ключевое слово break. разрыв выхода используется для выхода из цикла
public static IEnumerable<int> GetDiff(int start, int end)
{
while (true)
{
if(start > end) //Exit condition
yield break; // this will exit from while loop
yield return start; // This will return value of start to IEnumerable
start++;
}
//yield break; // No need to use here
}
Теперь вы можете использовать функцию GetDiff()
как,
public static void Print()
{
foreach(var item in GetDiff(1, 10))
Console.WriteLine(item);
}
POC: .Net Fiddle