DEV Community

loading...

C# - The For Loop Paradox

Ricky Stam
Technical Consultant @Microsoft. I love to work with C# and .NET but also enjoy writing JavaScript and Angular. Opinions are my own.
・2 min read

For Loop Paradox

So first of all what is the for loop paradox?

It happens when you use i value of a for loop inside an async Task. What will happen is that your task will run only using the last value of i. Maybe if your for loop is very big some of your Tasks might get also some intermediate values.

But let's see some code to demonstrate this behavior:

class Program
{
    static async Task Main(string[] args)
    {
        var tasks = new List<Task>();

        for (var i = 0; i <= 9; i++)
        {
            tasks.Add(Task.Run(() =>
            {
                Console.WriteLine(i);
            }));
        }

        await Task.WhenAll(tasks.ToArray());
    }
}

The expected result would be to print on the console all the values from 0 to 9 in a random order since these are Tasks and ordering is not guaranteed.

But let's see what we actually get:

Alt Text

Hmmm... something is wrong we are actually printing only the number 10 which is not expected at all...

Why is this happening?

This is a common behavior with closures, what happen is that this piece of code:

tasks.Add(Task.Run(() =>
        {
            Console.WriteLine(i);
        }));

is using the value that i has at the time of the execution.

Closures close over variables, not over values.

Ok, but how to work around this behavior?

The fix is pretty simple, just assign the i value to a new variable.

class Program
{
    static async Task Main(string[] args)
    {
        var tasks = new List<Task>();

        for (var i = 0; i <= 9; i++)
        {
            // work around for closure behavior
            var j = i;

            tasks.Add(Task.Run(() =>
            {
                // use the new j variable here
                Console.WriteLine(j);
            }));
        }

        await Task.WhenAll(tasks.ToArray());
    }
}

Now every time we iterate over the for loop, we create a new variable j. Each closure is closed over a different j variable, which is assigned only once and keeps the correct current value of i variable at each iteration.

Now in our console we get the expected results!

Alt Text

History Lesson Incoming!

This behavior was also happening when using foreach loop but the C# Team did a breaking change when they released the C# 5 and "fix" this behavior.

But for the for loop they decided to keep it as it is.

More resources

You can read more about closures in C# and how to use them to your advantage on the article of one and only Jon Skeet The Beauty of Closures

I hope that by reading this article I'll save you some debug time!

This post was written with love ❤️

Discussion (0)