Working with yield in C#: Enhancing Efficiency and Code Clarity
Tags: syncfusion, csharp, development, web
Published: 2024-03-26 03:00:19

---
title: Working with yield in C#: Enhancing Efficiency and Code Clarity
published: true
date: 2024-03-18 12:01:29 UTC
tags: syncfusion, csharp, development, web
canonical_url: https://www.syncfusion.com/blogs/post/yield-in-csharp.aspx
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cmxx1a9pnh7oaue4fv90.png
---
**TLDR:** Exploring C#’s yield keyword for efficient code usage. Understanding its history, usage, and essential requirements to make cleaner code and reduce memory usage.

Programming in C# offers many features. One of them, introduced in C# version 2.0, is a powerful feature called **yield**. Despite its complexity, understanding yield correctly can greatly benefit your projects.

In this blog, we’ll delve into the yield keyword and its usage in C#, focusing specifically on memory efficiency. We’ll explore how yield can help you reduce memory consumption while handling large data sets.

## Understanding the history

Yield was introduced in C# 2.0. Before its introduction, iterating over a collection required implementing a custom iterator using the **IEnumerator** interface. This involved creating a class that implemented **IEnumerator** and **IEnumerable** interfaces and managing the iterator’s state manually.

## What is the yield keyword?

The yield keyword is used for stateful iteration over a collection in a customized manner. The yield keyword informs the compiler that the method that contains it is an iterator block.

In other words, yield will provide the next element or value from the sequence when iterating a sequence. We don’t need to wait to complete the iteration to get the items from the iterating sequence. It will return one element in the collection at a time. Simply, when using the **yield return** statement, we don’t need to create a temporary collection (array, list, etc.) to store the data and eventually return all the data together.

## Two main concepts of yield in C-sharp

The [yield](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/yield "yield statement in C# documentation") statement has the two following forms:

- **yield return<expression>:** This statement returns one element at a time and requires **IEnumerable** or **IEnumerator** as the return type.

- **yield break:** This statement ends the iterator block.

Let’s understand these concepts with some examples.

## yield example with C-sharp

First, let’s discuss the example using the classic approach in C#.

```csharp
var numList = new List<int>()
{
    1, 4, 5, 7, 4, 10
};

var sumResults = SumOfNums(numList);

foreach (var sumResult in sumResults)
{
    Console.WriteLine(sumResult);
}
    
IEnumerable<int> SumOfNums(List<int> nums)
{
    var results = new List<int>();
    var sum = 0;
    foreach (var num in nums)
    {
        sum += num;
        results.Add(sum);
    }

    return results;
}
```

In this example, we calculated the sum of the values in the **numList** list using the **SumOfNums** method. Inside the **SumOfNums** method, we created a list to return the results list.

After running the previous code examples, we’ll get the following output.

```csharp
Output : 
1
5
10
17
21
31
```

If we have thousands of elements in the array, we must wait until it returns the results list using this approach. It would slow the computation and require a significant amount of memory to execute.

Now, let’s understand how this classic approach works using a simplified flow for the previous example. ![Flow chart diagram](https://www.syncfusion.com/blogs/wp-content/uploads/2024/03/A-diagram-of-a-process.png)

In this flow chart:

1. **Main Program:** This marks the program’s beginning, serving as the entry point of our code.
2. **Initialization:** Within the **Main Program**, we initialize a variable called **numList**, comprising a series of integers.
3. **Method call:** The program calls the **SumOfNums** method, passing **numList** as an argument. This method call is depicted as an arrow leading to the **SumOfNums** blocks.
4. **SumOfNums method:** Inside the **SumOfNums** method, several components come into play:
  1. **Initialization:** This step involves creating a **results** list to store intermediate results.
  2. **Sum initialization:** A **sum** variable is initialized to 0 to accumulate the sum of the numbers.
  3. **Foreach loop (num):** A **foreach** loop iterates through each element (num) in the **nums** list.
  4. **Sum update:** Inside the loop, the **sum** variable is updated by adding the current **num** to it, calculating the cumulative sum of the numbers.
  5. **Results update:** The **sum** value is added to the **results** list, storing each intermediate result.
  6. **End of Foreach loop:** After processing all elements in the **nums** list, the loop ends.
  7. **Return results:** The **results** list, now containing all the intermediate sums, is returned from the **SumOfNums** method.
5. **Main Program (continued):** The returned results list is stored in the **sumResults** variable in the **Main Program**.
6. **Foreach loop (sumResult):** The program enters another **foreach** loop, iterating through the elements of **sumResults**, which is of type **IEnumerable**.
7. **Printing:** Inside this **foreach** loop, each **sumResult** value is printed using **WriteLine(sumResult)**.

Refer to the following code example to understand how using the yield keyword improves this approach.

```csharp
var numList = new List<int>()
{
    1, 4, 5, 7, 4, 10
};

foreach (var result in SumOfNums(numList))
{
    Console.WriteLine(result);
}

IEnumerable<int> SumOfNums(List<int> nums)
{
    var sum = 0;
    foreach (var num in nums)
    { 
        sum += num;
        yield return sum;
    }
}
```

We have used the **yield** keyword, so we don’t need to wait until the **SumOfNums** method is completed to print the results like in the previous example.

When the **SumOfNums** method is called, it executes until it reaches the **yield return** statement. At that point, the control shifts to the preceding **foreach** loop, which runs the **SumOfNums** method. Then, it will print the result and proceed to the next iteration of the **SumOfNums** method. This process continues until the iteration of the entire list is completed.

Following is the output of the example. There isn’t any change in the output.

```csharp
Output : 
1
5
10
17
21
31
```

For the previous example, let’s understand how this yield approach functions through a simplified flow. ![Flowchart diagram](https://www.syncfusion.com/blogs/wp-content/uploads/2024/03/A-diagram-of-a-program.png)

In this flow chart:

1. **Main Program:** The program initiates in the **Main Program** section, serving as the code’s entry point.
2. **Initialization:** Within the **Main Program**, we initialize a **numList** variable, representing a list containing a series of integers: **[1, 4, 5, 7, 4, 10].**
3. **Method call:** Next, the program calls the **SumOfNums(numList)** method, passing **numList** as an argument. This method call is depicted as an arrow leading to the **SumOfNums()** block.
4. **SumOfNums method:** Within the **SumOfNums()** method, several components come into play:
  1. **Initialization:** We initialize a **sum** variable to 0.
  2. **Foreach loop:** A **foreach** loop iterates through each element of the **numList** passed as an argument.
  3. **Accumulation:** Inside the loop, we update the **sum** variable by adding the current element from **numList**  to it.
  4. **Yield return:** After updating the **sum,** we encounter a **yield return** statement, which yields the current value of the **sum** to the calling code. This characteristic of the yield keyword facilitates the generation and return of values one at a time, conserving memory.
5. **Main Program (continued):** The control returns to the **Main Program** after each **yield return** statement. In this section, we have a **foreach** loop that iterates over the values returned by the **SumOfNums()** method.
6. **Printing:** Inside the **foreach** loop, each value (represented by a result) is printed using **Console.WriteLine(result)**.

### Understanding the memory usage of each approach

Let’s understand how memory usage works with each approach.

#### Memory usage using the classic approach

First, consider an example of handling a huge array of elements using the classic approach.

Refer to the following code example.

```csharp
var arr = new int[1000];

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var result = SumOfNums(arr.ToList());
stopwatch.Stop();
Console.WriteLine("Time elapsed using classic approach: " + stopwatch.ElapsedMilliseconds + "ms");

long memoryBefore = GC.GetTotalMemory(true);
result = SumOfNums(arr.ToList());
long memoryAfter = GC.GetTotalMemory(true);
Console.WriteLine("Memory used using classic approach: " + (memoryAfter - memoryBefore) + "bytes");
```

In the previous example, we executed the **SumOfNums** method for the classic approach and then measured the time and memory consumption for it. Refer to the following output.

```csharp
Time elapsed using classic approach: 0ms
Memory used using classic approach: 8508bytes
```

#### Memory usage using the yield approach

Let’s also consider an example with a large array of elements using the yield approach to measure the time and memory consumption.

```csharp
var arr = new int[1000];

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var result = SumOfNums(arr.ToList());
stopwatch.Stop();
Console.WriteLine("Time elapsed using yield approach: " + stopwatch.ElapsedMilliseconds + "ms");

long memoryBefore = GC.GetTotalMemory(true);
result = SumOfNums(arr.ToList());
long memoryAfter = GC.GetTotalMemory(true);
Console.WriteLine("Memory used using yield approach: " + (memoryAfter - memoryBefore) + "bytes");
```

In this example, we’ve executed the **SumOfNums** method in the yield approach and then measured the time and memory consumption using this approach to compare it with the classic approach.

Refer to the following output for the measured results of time and memory consumption using the yield approach.

```csharp
Time elapsed using yield approach: 0ms
Memory used using yield approach: 4112bytes
```

As you can see, the yield approach is much more memory-efficient, consuming only **4,112 bytes** of memory compared to the classic approach’s **8,508 bytes**. This memory optimization becomes increasingly crucial when handling large data sets or sequences. While the elapsed time may be similar in both approaches, the memory savings provided by the yield approach can enhance the overall app performance.

## yield break

The **yield break** statement serves to terminate the iterator (array, list, etc.) of the block. Let’s explore the function of the **yield break** statement with an illustrative example.

```csharp
var numList = new List<int>()
{
    1, 4, 5, 7, 4, 10
};

foreach (var result in SumOfNums(numList))
{
    Console.WriteLine(result);
}

IEnumerable<int> SumOfNums(List<int> nums)
{
    var sum = 0;
    for (int i = 0; i < nums.Count; i++)
    {
        if (i == 2)
        {
            yield break;
        }
        sum += nums[i];
        yield return sum;
    }
}
```

In the code example, an **if** condition statement is used to check whether the value of **i** is equal to 2. Upon satisfying this condition, the **yield break** statement is executed, effectively halting the iteration of the block. Consequently, no further items are returned. This usage of the **yield break** statement enables the termination of the iterator within the block.

After executing the previous code examples, we’ll get the following output:

```csharp
Output : 
1
5
```

From this output, we observe that only two results are printed. This outcome arises from the termination of the **for** loop when the value of i equals 2, thereby concluding the iteration.

## The usage of yield with memory usage

The **yield** keyword is a powerful tool for memory-efficient programming, especially in scenarios involving large data sets or sequences.

Let’s delve deeper into how yield influences memory usage:

- **Lazy evaluation:** Yield enables lazy evaluation, allowing elements to be generated and returned one at a time as needed. This approach contrasts with generating and storing all elements in memory simultaneously. It avoids the necessity for a substantial up-front memory allocation.
- **No intermediate collection:** In the absence of yield, the creation of intermediate collections (e.g., lists) to store results before their return becomes necessary. These collections consume memory. Conversely, yield eliminates the need for such collections, resulting in significant memory savings.
- **Iterative consumption:** With yield, the memory footprint remains relatively constant, regardless of the data set’s size. We only need memory to store the current state of the iteration, not the entire data set.  
- **Reduced garbage collection:** When we create intermediate collections, they become objects that need to be managed by the garbage collector. Excessive memory usage can trigger more frequent garbage collections, impacting performance. The **yield** reduces the number of objects to be collected, leading to more efficient memory management.
- **Streaming:** The **yield** facilitates data processing in a streaming fashion, particularly advantageous when dealing with external data sources or data sets that exceed memory capacity. It enables immediate processing of available data without necessitating the entire data set’s loading into memory.
- **Reduced memory pressure:** In scenarios featuring limited available memory, the utilization of yield aids in alleviating memory pressure. By retaining only a minimal amount of data in memory at any given time, programs become more memory-efficient.

## Requirements for using yield

Certain prerequisites must be met to utilize the yield keyword effectively:

- Avoid using yield within an **unsafe block**.
- Avoid using **ref** or **out** keywords with method parameters and properties.
- The use of a **yield return** statement within a **try-catch** block is prohibited. However, it is permissible within a **try** statement situated inside the **try-finally** block.
- **Yield break** statements may be utilized within **try-catch** and **try-finally** blocks.

## Conclusion

Thanks for reading! This article has discussed the yield keyword in C# with a few examples. We have also listed a few requirements that should be met to use the yield keyword. The point to highlight is that when we use the yield keyword with a huge iteration process, it will require less memory than usual. Furthermore, it will help to reduce the lines of code and maintain the code cleanly.

I hope this article helped you learn about the yield keyword in C#. Feel free to share your opinions or experiences in the comment section below. For questions, you can contact us through our [support forum](https://www.syncfusion.com/forums "Syncfusion Support Forum"), [support portal](https://support.syncfusion.com/ "Syncfusion Support Portal"), or [feedback portal](https://www.syncfusion.com/feedback "Syncfusion Feedback Portal"). We are happy to assist you!

## Related blogs

- [Syncfusion Essential Studio 2024 Volume 1 Is Here!](https://www.syncfusion.com/blogs/post/essential-studio-2024-vol-1.aspx "Blog: Syncfusion Essential Studio 2024 Volume 1 Is Here!")
- [Exploring the Features of C# 11: The Modern Capabilities of a Vibrant Language [Webinar Show Notes]](https://www.syncfusion.com/blogs/post/exploring-csharp-11-features.aspx "Blog: Exploring the Features of C# 11: The Modern Capabilities of a Vibrant Language [Webinar Show Notes]")
- [Struct vs. Record vs. Class in C#](https://www.syncfusion.com/blogs/post/struct-record-class-in-csharp.aspx "Blog: Struct vs. Record vs. Class in C#")
- [Get Started with Tuples in C#: A Beginner’s Handbook](https://www.syncfusion.com/blogs/post/working-with-tuple-in-csharp.aspx "Blog: Get Started with Tuples in C#: A Beginner’s Handbook")
- [Pattern Matching in C# for Beginners](https://www.syncfusion.com/blogs/post/pattern-matching-in-c-for-beginners.aspx "Blog: Pattern Matching in C# for Beginners")