C# IEnumerable being reset in child method

I have the below method:

private static List<List<job>> SplitJobsByMonth(IEnumerable<job> inactiveJobs)
{
    List<List<job>> jobsByMonth = new List<List<job>>();

    DateTime cutOff = DateTime.Now.Date.AddMonths(-1).Date;
    cutOff = cutOff.AddDays(-cutOff.Day + 1);

    List<job> temp;
    while (inactiveJobs.Count() > 0)
    {
        temp = inactiveJobs.Where(j => j.completeddt >= cutOff).ToList();
        jobsByMonth.Add(temp);
        inactiveJobs = inactiveJobs.Where(a => !temp.Contains(a));
        cutOff = cutOff.AddMonths(-1);
    }

    return jobsByMonth;
}

It aims to split the jobs by month. 'job' is a class, not a struct. In the while loop, the passed in IEnumerable is reset with each iteration to remove the jobs that have been processed:

inactiveJobs = inactiveJobs.Where(a => !temp.Contains(a));

Typically this reduces the content of this collection by quite a lot. However, on the next iteration the line:

temp = inactiveJobs.Where(j => j.completeddt >= cutOff).ToList();

restores the inactiveJobs object to the state it was when it was passed into the method - so the collection is full again. I have solved this problem by refactoring this method slightly, but I am curious as to why this issue occurs as I can't explain it. Can anyone explain why this is happening?

Upvotes: 0

Views: 176

Answers (3)

Markiian Benovskyi
Markiian Benovskyi

Reputation: 2161

In LINQ, queries have two different behaviors of execution: immediate and deferred.

The query is actually executed when the query variable is iterated over, not when the query variable is created. This is called deferred execution.

You can also force a query to execute immediately, which is useful for caching query results.

In order to make this add .ToList() in the end of your line:

inactiveJobs = inactiveJobs.Where(a => !temp.Contains(a)).ToList();

This executes the created query immediately and writes result to your variable.

You can see more about this example Here.

Upvotes: 0

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726849

This happens because of deferred execution of LINQ's Where.

When you do this

inactiveJobs = inactiveJobs.Where(a => !temp.Contains(a));

no evaluation is actually happening until you start iterating the IEnumerable. If you add ToList after Where, the iteration would happen right away, so the content of interactiveJobs would be reduced:

inactiveJobs = inactiveJobs.Where(a => !temp.Contains(a)).ToList();

Upvotes: 0

Brett
Brett

Reputation: 1550

Why not just use a group by?

    private static List<List<job>> SplitJobsByMonth(IEnumerable<job> inactiveJobs)
    {
        var jobsByMonth = (from job in inactiveJobs
                            group job by new DateTime(job.completeddt.Year, job.completeddt.Month, 1)
                            into g
                            select g.ToList()).ToList();
        return jobsByMonth;
    }

Upvotes: 1

Related Questions