C#の可能性は年々広がっています。さまざまな機能がプログラマーの生活をより快適にしますが、それらのいくつかの目的と機能は誰にとっても明白ではないかもしれません。たとえば、古き良き収量。一部の開発者、特に初心者にとって、これは本当の魔法です-理解できないが興味深いものです。この記事では、yieldが実際にどのように機能するか、そしてこの魔法の言葉の背後に実際に何が隠されているかを示します。読書を楽しむ!
歩留まりが必要な理由
yield . - , . , . , .
, , . , .
, . yield . – , , .
, yield , C#. , yield . , yield C#.
yield
, . , :
IEnumerable
IEnumerable<T>
IEnumerator
IEnumerator<T>
yield , . .
, yield, . , yield- , :
static IEnumerator GetInts()
{
Console.WriteLine("first");
yield return 1;
Console.WriteLine("second");
yield return 2;
}
static void Main()
{
IEnumerator intsEnumerator = GetInts(); // print nothing
Console.WriteLine("..."); // print "..."
intsEnumerator.MoveNext(); // print "first"
Console.WriteLine(intsEnumerator.Current); // print 1
}
, GetInts , IEnumerator. . , .
MoveNext . yield return. , Current , yield return.
, "...", "first", 1 - , Current.
, MoveNext , , . , "second", Current 2.
, MoveNext , . , MoveNext false. false.
GetInts , , .
,
, yield-, MoveNext. :
IEnumerator GetNumbers()
{
string stringToPrint = "moveNext";
Console.WriteLine(stringToPrint); // print "moveNext"
yield return 0;
Console.WriteLine(stringToPrint); // print "moveNext"
stringToPrint = "anotherStr";
yield return 1;
Console.WriteLine(stringToPrint); // print "anotherStr"
}
GetNumbers MoveNext, "moveNext", - "anotherStr". .
. :
string message = "message1";
IEnumerator GetNumbers()
{
Console.WriteLine(message);
yield return 0;
Console.WriteLine(message);
yield return 1;
Console.WriteLine(message);
}
void Method()
{
var generator = GetNumbers();
generator.MoveNext(); // print "message1"
generator.MoveNext(); // print "message1"
message = "message2";
generator.MoveNext(); // print "message2"
}
GetNumbers , . , .
: , .
yield break
yield return yield break, , . MoveNext, yield break, false. , . , , yield, – -, '' yield break.
, yield break:
IEnumerator GenerateMultiplicationTable(int maxValue)
{
for (int i = 2; i <= 10; i++)
{
for (int j = 2; j <= 10; j++)
{
int result = i * j;
if (result > maxValue)
yield break;
yield return result;
}
}
}
2 10 . ( maxValue), . yield break.
IEnumerable
, , yield, IEnumerable, , . IEnumerable, , foreach.
. , IEnumerable, IEnumerable, IEnumerator. IEnumerator :). ? , .
:
void PrintFibonacci()
{
Console.WriteLine("Fibonacci numbers:");
foreach (int number in GetFibonacci(5))
{
Console.WriteLine(number);
}
}
IEnumerable GetFibonacci(int maxValue)
{
int previous = 0;
int current = 1;
while (current <= maxValue)
{
yield return current;
int newCurrent = previous + current;
previous = current;
current = newCurrent;
}
}
GetFibonacci , 1. , IEnumerable, foreach. PrintFibonacci.
, IEnumerable . , foreach GetEnumerator . GetEnumerator , . :
int _rangeStart;
int _rangeEnd;
void TestIEnumerableYield()
{
IEnumerable polymorphRange = GetRange();
_rangeStart = 0;
_rangeEnd = 3;
Console.WriteLine(string.Join(' ', polymorphRange)); // 0 1 2 3
_rangeStart = 5;
_rangeEnd = 7;
Console.WriteLine(string.Join(' ', polymorphRange)); // 5 6 7
}
IEnumerable GetRange()
{
for (int i = _rangeStart; i <= _rangeEnd; i++)
{
yield return i;
}
}
string.Join IEnumerable, GetRange. , , foreach. _rangeStart _rangeEnd – IEnumerable !
LINQ, , , - , LINQ- . . , .
, , . , , , . .
yield
, yield . , , . Unity.
, yield, , – LINQ. yield , , , . , , :
public IEnumerable EnumerateAncestors(SyntaxNode node)
{
while (node != null)
{
node = node.Parent;
yield return node;
}
}
, . - , – , . , yield ( ), :).
yield , , , . , - , . :
, IEnumerator Reset, , yield-, . Reset NotSupportedException. : - , Reset;
yield -;
yield , unsafe-;
yield return try-catch. try try-finally. yield break try try-catch, try-finally .
- ?
, yield-, dotPeek. GetFibonacci, maxValue:
IEnumerable GetFibonacci(int maxValue)
{
int previous = 0;
int current = 1;
while (current <= maxValue)
{
yield return current;
int newCurrent = previous + current;
previous = current;
current = newCurrent;
}
}
'Show compiler-generated code', dotPeek. GetFibonacci ?
, - :
[IteratorStateMachine(typeof(Program.d__1))]
private IEnumerable GetFibonacci(int maxValue)
{
d__1 getFibonacciD1 = new d__1(-2);
getFibonacciD1.<>4__this = this;
getFibonacciD1.<>3__maxValue = maxValue;
return (IEnumerable)getFibonacciD1;
}
, ? , . , .
(, IL):
[IteratorStateMachine(typeof(GetFibonacci_generator))]
private IEnumerable GetFibonacci(int maxValue)
{
GetFibonacci_generator generator = new GetFibonacci_generator(-2);
generator.forThis = this;
generator.param_maxValue = maxValue;
return generator;
}
, . . , , , C#-. . , , dotPeek ( – ildasm) :).
, , maxValue. '-2' – , , .
, , , . , .
:
class GetFibonacci_generator : IEnumerable,
IEnumerable,
IEnumerator,
IEnumerator,
IDisposable
, ... IDisposable! , , IEnumerator, GetFibonacci IEnumerable. , .
:
public GetFibonacci_generator(int startState)
{
state = startState;
initialThreadId = Environment.CurrentManagedThreadId;
}
, , '-2', . , , . , GetEnumerator:
IEnumerator IEnumerable.GetEnumerator()
{
GetFibonacci_generator generator;
if (state == -2 && initialThreadId == Environment.CurrentManagedThreadId)
{
state = 0;
generator = this;
}
else
{
generator = new GetFibonacci_generator(0);
generator.forThis = forThis;
}
generator.local_maxValue = param_maxValue;
return generator;
}
, , . . :
IEnumerable enumerable = prog.GetFibonacci(5);
IEnumerator enumerator = enumerable.GetEnumerator();
Console.WriteLine(enumerable == enumerator);
, 'True'. ? :)
, GetEnumerator state '0'. , .
:
generator.local_maxValue = param_maxValue;
GetFibonacci (, , ), , param_maxValue . local_maxValue.
, maxValue 2 – param_maxValue local_maxValue. , , . MoveNext:
bool IEnumerator.MoveNext()
{
switch (state)
{
case 0:
state = -1;
local_previous = 0;
local_current = 1;
break;
case 1:
state = -1;
local_newCurrent = local_previous + local_current;
local_previous = local_current;
local_current = local_newCurrent;
break;
default:
return false;
}
if (local_current > local_maxValue)
return false;
_current = local_current;
state = 1;
return true;
}
, GetFibonacci. MoveNext _current. Current .
( local_current > local_maxValue), '-1'. state – MoveNext - false.
, , MoveNext false, _current (, , Current) .
. state '-2'. , state = -2, MoveNext - false. , . , '-2' '0' GetEnumerator. MoveNext, GetEnumerator?
GetFibonacci – IEnumerable, , MoveNext . , , IEnumerable, IEnumerator, . MoveNext, GetEnumerator, ... false. , '' , .
. yield-, IEnumerable, , IEnumerable, IEnumerator. IEnumerator , , GetEnumerator. , , '', . :
IEnumerable enumerable = GetFibonacci(5);
IEnumerator deadEnumerator = (IEnumerator)enumerable;
for (int i = 0; i < 5; ++i)
{
if (deadEnumerator.MoveNext())
{
Console.WriteLine(deadEnumerator.Current);
}
else
{
Console.WriteLine("Sorry, your enumerator is dead :(");
}
}
IEnumerator enumerator = enumerable.GetEnumerator();
Console.WriteLine(deadEnumerator == enumerator);
for (int i = 0; i < 5; ++i)
{
if (deadEnumerator.MoveNext())
{
Console.WriteLine(deadEnumerator.Current);
}
else
{
Console.WriteLine("Sorry, your enumerator is dead :(");
}
}
, ? : 5 1, 1, 2, 3, 5.
IEnumerator. IEnumerable?
, GetEnumerator, , IEnumerable . , :
IEnumerable enumerable = GetInts(0);
IEnumerator firstEnumerator = enumerable.GetEnumerator();
IEnumerable firstConverted = (IEnumerable)firstEnumerator;
Console.WriteLine(enumerable == firstEnumerator);
Console.WriteLine(firstConverted == firstEnumerator);
Console.WriteLine(firstConverted == enumerable);
'True' , . , , ( - ) .
, IEnumerable GetEnumerator ( , )? , yield-:
IEnumerable RepeatLowerString(string someString)
{
someString.ToLower();
while (true)
{
yield return someString;
}
}
, . .
, ? RepeatLowerString, , , . ?
, ToLower , - , . , , , . ToLower, , , - , "" - .
- . , . , RepeatLowerString, . , " " – , .
, . PVS-Studio. , , C#, C, C++ Java. , PVS-Studio .
, , RepeatLowerString:
IEnumerable RepeatLowerString(string someString)
{
string lower = someString.ToLower();
while (true)
{
yield return lower;
}
}
, IEnumerable:
IEnumerable enumerable = RepeatLowerString("MyString");
IEnumerator firstEnumerator = enumerable.GetEnumerator();
IEnumerator secondEnumerator = enumerable.GetEnumerator();
var secondConverted = (IEnumerable)secondEnumerator;
var magicEnumerator = secondConverted.GetEnumerator();
for (int i = 0; i < 5; i++)
{
magicEnumerator.MoveNext();
Console.WriteLine(magicEnumerator.Current);
}
?
! , NullReferenceException. ?
, . , . , .
, magicEnumerator.MoveNext() ToLower. someString, param_someString local_someString:
public string param_someString;
private string local_someString;
, , , , MoveNext, , local_someString:
bool IEnumerator.MoveNext()
{
switch (this.state)
{
case 0:
this.state = -1;
this.local_lower = this.local_someString.ToLower();
break;
case 1:
this.state = -1;
break;
default:
return false;
}
this._current = this.local_lower;
this.state = 1;
return true;
}
, null . ?
GetEnumerator local_someString param_someString:
IEnumerator IEnumerable.GetEnumerator()
{
RepeatLowerString_generator generator;
if (state == -2 && initialThreadId == Environment.CurrentManagedThreadId)
{
state = 0;
generator = this;
}
else
{
generator = new RepeatLowerString_generator(0);
generator.forThis = forThis;
}
generator.local_someString = param_someString;
return generator;
}
, null ? . null? - :
IEnumerable enumerable = RepeatLowerString("MyString");
IEnumerator firstEnumerator = enumerable.GetEnumerator();
IEnumerator secondEnumerator = enumerable.GetEnumerator();
var secondConverted = (IEnumerable)secondEnumerator;
var magicEnumerator = secondConverted.GetEnumerator();
for (int i = 0; i < 5; i++)
{
magicEnumerator.MoveNext(); // NRE
Console.WriteLine(magicEnumerator.Current);
}
GetEnumerator , local_SomeString . param_someString? , – GetEnumerator . , – , null.
param_someString local_someString magicEnumerator! local_someString.ToLower().
. GetEnumerator this, IEnumerable. , param_*. yield-, - . :
IEnumerable GetPositive()
{
int i = 0;
while (true)
yield return ++i;
}
, 1. :
IEnumerable enumerable = GetPositive();
IEnumerator firstEnumerator = enumerable.GetEnumerator();
IEnumerator secondEnumerator = enumerable.GetEnumerator();
var secondConverted = (IEnumerable)secondEnumerator;
IEnumerator magicEnumerator = secondConverted.GetEnumerator();
for (int i = 0; i < 5; i++)
{
magicEnumerator.MoveNext();
Console.WriteLine(magicEnumerator.Current);
}
1 5. , :).
2
– , . , , , .
yield-:
IEnumerable GetInts(int i)
{
while (true)
{
yield return i++;
}
}
, , i. MoveNext :
bool IEnumerator.MoveNext()
{
switch (this.state)
{
case 0:
this.state = -1;
break;
case 1:
this.state = -1;
break;
default:
return false;
}
this._current = this.local_i++;
this.state = 1;
return true;
}
, , local_i, MoveNext. , GetEnumerator – – , param_i:
IEnumerator IEnumerable.GetEnumerator()
{
GetInts_generator generator;
if ( state == -2
&& initialThreadId == Environment.CurrentManagedThreadId)
{
state = 0;
generator = this;
}
else
{
generator = new GetInts_generator(0);
generator.forThis = forThis;
}
generator.local_i = param_i;
return generator;
}
param_i, , yield- GetInts:
[IteratorStateMachine(typeof(GetInts_generator))]
private IEnumerable GetInts(int i)
{
GetInts_generator generator = new GetInts_generator(-2);
generator.forThis = this;
generator.param_i = i;
return generator;
}
. - param_i? , , local_i?
yield- GetInts – IEnumerable. GetEnumerator. , . , :
IEnumerable enumerable = GetInts(0);
// enumerable.param_i = 0
IEnumerator firstEnumerator = enumerable.GetEnumerator();
// firstEnumerator.local_i = enumerable.param_i
Console.WriteLine(enumerable == firstEnumerator); // True
firstEnumerator.MoveNext();
// firstEnumerator.local_i++
firstEnumerator.MoveNext();
// firstEnumerator.local_i++
IEnumerator secondEnumerator = enumerable.GetEnumerator();
// secondEnumerator.local_i = ?
GetInts, -. param_i – '0'. firstEnumerator. , , enumerable. , GetEnumerator local_i param_i enumerable.
MoveNext. local_i, firstEnumerator, enumerable, .
IEnumerator. , local_i? , , yield- .
param_i. , local_i MoveNext, param_i . , local_i , GetEnumerator.
. , GetEnumerator, . , , yield-. .
IEnumerator
, yield-, IEnumerable. , IEnumerator, IEnumerable. , , IEnumerator.
, IEnumerable. , . , yield-, IEnumerator yield-, IEnumerable:
GetEnumerator;
initialThreadId;
.
, . , , yield-, IEnumerable, state '-2' GetEnumerator. state MoveNext false - .
, IEnumerator, GetEnumerator . '0' state .
Dispose
Dispose - , IEnumerable<T> IDisposable. Dispose . , Dispose . using.
:
using (var disposableVar = CreateDisposableObject())
{
....
}
using var disposableVar = CreateDisposableObject();
....
Dispose disposableVar ( ), ( ). using .
using yield- . , , using, Dispose. , , , Dispose , .
, Dispose Dispose . , , , using yield-.
:
static IEnumerable GetLines(string path)
{
using (var reader = new StreamReader(path))
{
while (!reader.EndOfStream)
yield return reader.ReadLine();
}
}
, . using GetEnumerator, :
private void Finally1()
{
this.state = -1;
if (this.local_reader == null)
return;
this.local_reader.Dispose();
}
, Dispose state , MoveNext ( ) - false.
finally- – using MoveNext Dispose. Dispose :
void IDisposable.Dispose()
{
switch (this.state)
{
case -3:
case 1:
try
{
}
finally
{
this.Finally1();
}
break;
}
}
, using (, , ). , :).
Dispose , . , , IDisposable . , , , .
, MoveNext:
bool IEnumerator.MoveNext()
{
try
{
switch (this.state)
{
case 0:
this.state = -1;
this.local_reader = new StreamReader(this.local_path);
this.state = -3;
break;
case 1:
this.state = -3;
break;
default:
return false;
}
if (!this.local_reader.EndOfStream)
{
this._current = this.local_reader.ReadLine();
this.state = 1;
return true;
}
this.Finally1();
this.local_reader = null;
return false;
}
fault
{
Dispose();
}
}
, using yield-. fault. C# , IL-. : try , , fault. , , ! ? fault :).
, , Dispose , using, , . .
Reset!
, Reset :
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
, – NotSupportedException. , , , Reset. , .
, yield C#. – , , yield , - ' ', .
'' , yield return try-catch. , , yield-, . , - , .
, yield, . , , , , , , LINQ. , , ' ', , - List :).
, Twitter – , , .
, . !
, : Nikita Lipilin. What Is yield and How Does It Work in C#?.