Enhance your code with Yield keyword
What we'll cover
- Custom iteration
- Stateful iteration
Requirements
- Basic knowledge of C#
- Visual Studio or IDE of choice
Custom Iteration
Have you faced a situation where you have to iterate a collection and extract part of it? A common approach is to create a temporary collection type, e.g. List<T>
, add items to the list inside a loop, and finally return the new list. Let's see a simple example.
using System;
using System.Collections.Generic;
using DAL;
namespace ABClothing
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("TOP PURCHASE OF THE WEEK");
Console.WriteLine("***************************");
Console.WriteLine(" ");
foreach (var customer in GetTopPurchase())
{
Console.WriteLine($"ID: {customer.CustomerId} - " +
$"{customer.Name}: {string.Format("{0:C}", customer.TotalPurchase)}");
}
}
static IEnumerable<Customer> GetTopPurchase()
{
var db = new Database();
var customers = db.GetCustomers();
var tempList = new List<Customer>();
foreach (var customer in customers)
{
// Top purchase is $2000 and above
if (customer.TotalPurchase >= 2000m)
{
tempList.Add(customer);
}
}
return tempList;
}
}
}
// Output
TOP PURCHASE OF THE WEEK
***************************
ID: 2 - Kulas Light: $2,300.10
ID: 3 - Ervin Howell: $2,750.20
ID: 5 - Patricia Lebsack: $2,200.35
In the example above, the code is printing customers with purchase up to $2,000 or above. Database
method GetCustomers()
returns a list of customers. The if
condition filters out customers with purchase less than $2000. Customers with purchase amount up to $2000 or above are added to the temporary list tempList
, and the list is returned when the loop ends.
Simple, isn't it? But we can do better. Let's change the code to use yield
.
Stateful Iteration with yield
Below is GetTopPurchase()
method modified with yield return customer
.
...
static IEnumerable<Customer> GetTopPurchase()
{
var db = new Database();
var customers = db.GetCustomers();
decimal _total = 0m; // new line added
foreach (var customer in customers)
{
// Top purchase is $2000 and above
if (customer.TotalPurchase >= 2000m)
{
_total += customer.TotalPurchase; // new line added
yield return customer; // new line added
}
}
}
We no longer need tempList
. Let's take a closer look and examine what's happening here.
According to documentation, the yield
keyword is used in iterators for example a foreach
. It removes the need to store state of the enumeration in a temporary variable. The call to GetTopPurchase()
in the Main
method returns an IEnumerable<Customer>
into the foreach
. Because IEnumerable<T>
is returned, execution is deferred until the iterator is called. yield
returns each element one at a time. You can end the iteration by using yield break
statement.
In the first example above, tempList
is used to store state. With yield
that is not required because yield
keeps track of the current iteration and the state of variables within the block. For example, yield
remembers the state of _total
variable for every iteration. This is possible because "when a yield return
statement is reached in the iterator method, expression
is returned, and the current location [block or scope] in code is retained. Execution is restarted from that location the next time that the iterator function is called". In our example, execution resumes at if
statement the next time the iterator is called because it is the beginning of the scope where yield return
is used.
Exception Handling:
Ayield return
statement can't be located in atry-catch
block.
Ayield return
statement can be located in the try block of atry-finally
statement.
Ayield break
statement can be located in a try block or a catch block but not a finally block.
If the foreach body (outside of the iterator method) throws an exception, a finally block in the iterator method is executed.
Why use yield
?
- Efficiency: You can iterate large sets of data with efficient use of memory. In the first example with
tempList
, 5 records are returned and 3 displayed. In the second example (withyield
), one record is loaded into memory per iteration (when theif
condition evaluates to true). No unused records lying around in memory.
Further reading
- Visit documentation page for
yield
contextual keyword to learn more. - There is an interesting stackoverflow thread on the subject.
- Ash did a good job of explaining the benefits of using the
yield
keyword on this stackoverflow question: What are real life applications of yield?. I think you'll find it worth reading.
This is where I wrap up on this subject. I hope I have achieved my goal - to simplify the concept of yield
. I am always looking for ways to improve, so I'll appreciate your comments. Do not forget to share and subscribe to my newsletter to stay up to date with my posts.
Comments