Chapter120:YieldKeyword

No Comments

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.

Section120.1:SimpleUsage

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.

Section120.3:EarlyTermination

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.

Section120.5:LazyEvaluation

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.

Section120.6:Try…finally

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

Section120.7:Eagerevaluation

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):

  1. C#compilergeneratesaclassimplementingIEnumerable<BigInteger>andIEnumerator<BigInteger>(<Fibonacci>d0inildasm).
  2. Thisclassimplementsastatemachine.Stateconsistsofcurrentpositioninmethodandvaluesoflocal variables.
  3. 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 posts
No Comments