When you use the yield keyword in a statement, you indicate that the method, operator, or get accessor in which it appears is an iterator. Using yield to define an iterator removes the need for an explicit extra class (the class that holds the state for an enumeration) when you implement the IEnumerable and IEnumerator pattern for a custom collection type.
The yieldkeyword is used to define a function which returns an IEnumerableor IEnumerator(as well as their derivedgenericvariants)whosevaluesaregeneratedlazilyasacalleriteratesoverthereturnedcollection.Read more about the purpose in the remarks section.
Thefollowingexamplehasayieldreturnstatementthat’sinsideaforloop.
publicstaticIEnumerable<int>Count(intstart,intcount)
{
for(inti=0;i<=count;i++)
{
yieldreturnstart+i;
}
}
Thenyoucancallit:
foreach(intvalueinCount(start:4,count:10))
{
Console.WriteLine(value);
}
ConsoleOutput
4
5
6
… 14
LiveDemoon.NETFiddle
EachiterationoftheforeachstatementbodycreatesacalltotheCountiteratorfunction.Eachcalltotheiterator functionproceedstothenextexecutionoftheyieldreturnstatement,whichoccursduringthenextiterationof the forloop.
Section120.2:Correctlycheckingarguments
Aniteratormethodisnotexecuteduntilthereturnvalueisenumerated.It’sthereforeadvantageoustoassert preconditions outside of the iterator.
publicstaticIEnumerable<int>Count(intstart,intcount)
{
//Theexceptionwillthrowwhenthemethodiscalled,notwhentheresultisiterated
if(count<0)
thrownewArgumentOutOfRangeException(nameof(count));
returnCountCore(start,count);
}
privatestaticIEnumerable<int>CountCore(intstart,intcount)
{
//IftheexceptionwasthrownhereitwouldberaisedduringthefirstMoveNext()
//callontheIEnumerator,potentiallyatapointinthecodefarawayfromwhere
//anincorrectvaluewaspassed.
for(inti=0;i<count;i++)
{
yieldreturnstart+i;
}
}
CallingSideCode(Usage):
//Getthecount
varcount=Count(1,10);
//Iteratetheresults
foreach(varxincount)
{
Console.WriteLine(x);
}
Output:
1
2
3
4
5
6
7
8
9
10
LiveDemoon.NETFiddle
Whenamethodusesyieldtogenerateanenumerablethecompilercreatesastatemachinethatwheniterated overwillruncodeuptoayield.Itthenreturnstheyieldeditem,andsavesitsstate.
This means you won’t find out about invalid arguments (passing nulletc.) when you first call the method (because that creates the state machine), only when you try and access the first element (because only then does the code within the method get ran by the state machine). By wrapping it in a normal method that first checks argumentsyou can check them when the method is called. This is an example of failing fast.
When using C# 7+, the CountCorefunction can be conveniently hidden into the Countfunction as a local function. See example here.
Youcanextendthefunctionalityofexistingyieldmethodsbypassinginoneormorevaluesorelementsthatcould define a terminating condition within the function by calling a yieldbreakto stop the inner loop from executing.
publicstaticIEnumerable<int>CountUntilAny(intstart,HashSet<int>earlyTerminationSet)
{
intcurr=start;
while(true)
{
if(earlyTerminationSet.Contains(curr))
{
//we’vehitoneoftheendingvalues
yieldbreak;
}
yieldreturncurr;
if(curr==Int32.MaxValue)
{
//don’toverflowifwegetallthewaytotheend;juststop
yieldbreak;
}
curr++;
}
}
Theabovemethodwoulditeratefromagivenstartpositionuntiloneofthevalueswithinthe
earlyTerminationSetwasencountered.
//Iteratefromastartingpointuntilyouencounteranyelementsdefinedas
//terminatingelements
varterminatingElements=newHashSet<int>{7,9,11};
//Thiswilliteratefrom1untiloneoftheterminatingelementsisencountered(7)
foreach(varxinCountUntilAny(1,terminatingElements))
{
//Thiswillwriteouttheresultsfrom1until7(whichwilltriggerterminating)
Console.WriteLine(x);
}
Output:
1
2
3
4
5
6
LiveDemoon.NETFiddle
Section120.4:MorePertinentUsage
publicIEnumerable<User>SelectUsers()
{
//ExecuteanSQLqueryonadatabase.
using(IDataReaderreader=this.Database.ExecuteReader(CommandType.Text,”SELECTId,NameFROM Users”))
{
while(reader.Read())
{
intid=reader.GetInt32(0);string name =
reader.GetString(1);
yieldreturnnewUser(id,name);
}
}
}
ThereareotherwaysofgettinganIEnumerable<User>fromanSQLdatabase,ofcourse–thisjustdemonstrates thatyoucanuseyieldtoturnanythingthathas”sequenceofelements”semanticsintoanIEnumerable<T>that someone can iterate over.
Onlywhentheforeachstatementmovestothenextitemdoestheiteratorblockevaluateuptothenextyield
statement.
Considerthefollowingexample:
privateIEnumerable<int>Integers()
{
vari=0; while(true)
{
Console.WriteLine(“Insideiterator:”+i);
yieldreturni;
i++;
}
}
privatevoidPrintNumbers()
{
varnumbers=Integers().Take(3);
Console.WriteLine(“Startingiteration”);
foreach(varnumberinnumbers)
{
Console.WriteLine(“Insideforeach:”+number);
}
}
Thiswilloutput:
Starting iteration
Insideiterator:0
Insideforeach:0
Insideiterator:1
Insideforeach:1
Insideiterator:2
Insideforeach:2
ViewDemo
Asaconsequence:
- “Startingiteration”isprintedfirsteventhoughtheiteratormethodwascalledbeforethelineprintingit because the line Integers().Take(3);does not actually starts iteration (no call to IEnumerator.MoveNext()was made)
- Thelinesprintingtoconsolealternatebetweentheoneinsidetheiteratormethodandtheoneinsidetheforeach,ratherthanalltheonesinsidetheiteratormethodevaluatingfirst
- Thisprogramterminatesduetothe.Take()method,eventhoughtheiteratormethodhasawhiletrue
which it never breaks out of.
Ifaniteratormethodhasayieldinsideatry…finally,thenthereturnedIEnumeratorwillexecutethefinally
statementwhenDisposeiscalledonit,aslongasthecurrentpointofevaluationisinsidethetryblock.
Giventhefunction:
privateIEnumerable<int>Numbers()
{
yieldreturn1;
try
{
yieldreturn2;
yieldreturn3;
}
finally
{
Console.WriteLine(“Finallyexecuted”);
}
}
Whencalling:
privatevoidDisposeOutsideTry()
{
varenumerator=Numbers().GetEnumerator();
enumerator.MoveNext(); Console.WriteLine(enumerator.Current);
enumerator.Dispose();
}
Thenitprints:
1
View DemoWhen calling:
privatevoidDisposeInsideTry()
{
varenumerator=Numbers().GetEnumerator();
enumerator.MoveNext(); Console.WriteLine(enumerator.Current);
enumerator.MoveNext(); Console.WriteLine(enumerator.Current);
enumerator.Dispose();
}
Thenitprints:
1
2
Finallyexecuted
ViewDemo
The yieldkeyword allows lazy-evaluation of the collection. Forcibly loading the whole collection into memory is called eagerevaluation.
Thefollowingcodeshowsthis:
IEnumerable<int>myMethod()
{
for(inti=0;i<=8675309;i++)
{
yieldreturni;
}
}
…
//definetheiterator
varit=myMethod.Take(3);
//forceitsimmediateevaluation
//listwillcontain0,1,2
varlist=it.ToList();
Calling ToList, ToDictionaryor ToArraywill force the immediate evaluation of the enumeration, retrieving all the elements into a collection.
Section 120.8: Using yield to create an IEnumerator<T>when implementingIEnumerable<T>
TheIEnumerable<T>interfacehasasinglemethod,GetEnumerator(),whichreturnsanIEnumerator<T>.
WhiletheyieldkeywordcanbeusedtodirectlycreateanIEnumerable<T>,itcanalsobeusedinexactlythesame waytocreateanIEnumerator<T>.Theonlythingthatchangesisthereturntypeofthemethod.
ThiscanbeusefulifwewanttocreateourownclasswhichimplementsIEnumerable<T>:
publicclassPrintingEnumerable<T>:IEnumerable<T>
{
privateIEnumerable<T>_wrapped;
publicPrintingEnumerable(IEnumerable<T>wrapped)
{
_wrapped=wrapped;
}
//ThismethodreturnsanIEnumerator<T>,ratherthananIEnumerable<T>
//Buttheyieldsyntaxandusageisidentical.
publicIEnumerator<T>GetEnumerator()
{
foreach(varitemin_wrapped)
{
Console.WriteLine(“Yielding: “+ item);
yieldreturnitem;
}
}
IEnumeratorIEnumerable.GetEnumerator()
{
returnGetEnumerator();
}
}
(Note that this particular example is just illustrative, and could be more cleanly implemented with a single iterator method returning an IEnumerable<T>.)
Section120.9:LazyEvaluationExample:FibonacciNumbers
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Numerics;//alsoaddreferencetoSystem.Numberics
namespaceConsoleApplication33
{
classProgram
{
privatestaticIEnumerable<BigInteger>Fibonacci()
{
BigIntegerprev=0;
BigIntegercurrent=1;
while(true)
{
yieldreturncurrent;
varnext=prev+current;
prev=current;
current=next;
}
}
staticvoidMain()
{
//printFibonaccinumbersfrom10001to10010
varnumbers=Fibonacci().Skip(10000).Take(10).ToArray(); Console.WriteLine(string.Join(Environment.NewLine,numbers));
}
}
}
How itworks underthe hood(I recommendto decompileresulting .exefile inIL Disaamblertool):
- C#compilergeneratesaclassimplementingIEnumerable<BigInteger>andIEnumerator<BigInteger>(<Fibonacci>d0inildasm).
- Thisclassimplementsastatemachine.Stateconsistsofcurrentpositioninmethodandvaluesoflocal variables.
- ThemostinterestingcodeareinboolIEnumerator.MoveNext()method.Basically,whatMoveNext()do:
- Restorescurrentstate.Variableslikeprevandcurrentbecomefieldsinourclass(<current>52and<prev>51inildasm).Inourmethodwehavetwopositions(<>1state):firstattheopeningcurly brace, second at yieldreturn.
- Executescodeuntilnextyieldreturnoryieldbreak/}.
- Foryieldreturnresultingvalueissaved,soCurrentpropertycanreturnit.trueisreturned.Atthis pointcurrentstateissavedagainforthenextMoveNextinvocation.
- Foryieldbreak/}methodjustreturnsfalsemeaningiterationisdone.
Alsonote,that10001thnumberis468byteslong.Statemachineonlysavescurrentandprevvariablesasfields. Whileifwewouldliketosaveallnumbersinthesequencefromthefirsttothe10000th,theconsumedmemory sizewillbeover4megabytes.Solazyevaluation,ifproperlyused,canreducememoryfootprintinsomecases.
Section120.10:Thedifferencebetweenbreakandyieldbreak
Using yieldbreakas opposed to breakmight not be as obvious as one may think. There are lot of bad examples on the Internet where the usage of the two is interchangeable and doesn’t really demonstrate the difference.
Theconfusingpartisthatbothofthekeywords(orkeyphrases)makesenseonlywithinloops(foreach,while…)So when to choose one over the other?
It’simportanttorealizethatonceyouusetheyieldkeywordinamethodyoueffectivelyturnthemethodintoan iterator.Theonlypurposeofthesuchmethodisthentoiterateoverafiniteorinfinitecollectionandyield(output) itselements.Oncethepurposeisfulfilled,there’snoreasontocontinuemethod’sexecution.Sometimes,it happensnaturallywiththelastclosingbracketofthemethod}.Butsometimes,youwanttoendthemethod prematurely.Inanormal(non-iterating)methodyouwouldusethereturnkeyword.Butyoucan’tusereturninan iterator, you have to use yieldbreak. In other words, yieldbreakfor an iterator is the same as returnfor a standardmethod.Whereas,thebreakstatementjustterminatestheclosestloop.
Let’sseesomeexamples:
///<summary>
///Yieldsnumbersfrom0to9
///</summary>
///<returns>{0,1,2,3,4,5,6,7,8,9}</returns>
publicstaticIEnumerable<int>YieldBreak()
{
for(inti=0;;i++)
{
if(i<10)
{
//Yieldsanumber
yieldreturni;
}
else
{
//Indicatesthattheiterationhasended,everything
//fromthislineonwillbeignored
yieldbreak;
}
}
yieldreturn10;//Thiswillnevergetexecuted
}
///<summary>
///Yieldsnumbersfrom0to10
///</summary>
///<returns>{0,1,2,3,4,5,6,7,8,9,10}</returns>
publicstaticIEnumerable<int>Break()
{
for(inti=0;;i++)
{
if(i<10)
{
//Yieldsanumber
yieldreturni;
}
else
{
//Terminatesjusttheloop
break;
}
}
//Executioncontinues
yieldreturn10;
}
Section 120.11: Return another Enumerable within a method returningEnumerable
publicIEnumerable<int>F1()
{
for(inti=0;i<3;i++)
yieldreturni;
//returnF2();//CompileError!!
foreach(varelementinF2())
yieldreturnelement;
}
publicint[]F2()
{
returnnew[] {3,4,5};
}
About us and this blog
We are a digital marketing company with a focus on helping our customers achieve great results across several key areas.
Request a free quote
We offer professional SEO services that help websites increase their organic search score drastically in order to compete for the highest rankings even when it comes to highly competitive keywords.
Subscribe to our newsletter!
More from our blog
See all postsRecent Posts
- Chapter153:C#Script 22/06/2024
- Chapter 152: Unsafe Code in.NET 21/06/2024
- Chapter151:ASP.NETIdentity 20/06/2024