Elena
Elena

Reputation: 839

Why does this happen IEnumerable versus List

I have the following BL method

public static void SomeMethod (List<SomeClass> group)
{
    IEnumerable<SomeClass> groupWithFalse =(from SomeClass gr in group
                                            where gr.SomeProp== false
                                            select gr);
    foreach (SomeClass grFalse in groupWithFalse)
    {
      grFalse.Save();
    }

    if (groupWithFalse.Any())
    {
      // do some stuff
    }
}

The mock implementation for dl saving (which cannot be changed as it used in a lot of unit tests) is:

public void Save()
{
   group.SomeProp = true;
}

If I try to unit test the last statement of the flow e.g if (groupWithFalse.Any()) the statement fails, as apparently there are no more elements with that property set to false. If I change the code in business logic to :

public static void SomeMethod (List<SomeClass> group)
{
    List<SomeClass> groupWithFalse = new List<SomeClass>();
    foreach (var g in group)
    {
      if (g.SomeProp == false)
          groupWithFalse.Add(g);
    }

    foreach (SomeClass grFalse in groupWithFalse)
    {
      grFalse.Save();
    }

    if (groupWithFalse.Any())
    {
      // do some stuff
    }
}

the conditional statement if (groupWithFalse.Any()) does not fail in unit tests. Why does this happen? Thanks

Upvotes: 0

Views: 163

Answers (5)

Restuta
Restuta

Reputation: 5903

In the first example you are using IEnumerable. Think about it like a query, not a list of objects. So then you are iterating through entire collection "group" using above query that says "iterate only elements that have SomeProp equal to false".

Then in your foreach statement you modify the state of the object in "group" list.

And then you are applying additional predicate on you query that states "Are there any elements in "group" list that have some prop equal to false?" And you obviously recieve an answer "no there are none of them".

In the second example you just getting new collection of elements, NOT a query. So then you iterate over it, chage state and then ask "Are there any elements?" And recive an "yes" answer. There are the same elements as before but with changed SomeProp property value.

Upvotes: 0

jason
jason

Reputation: 241641

Okay, in the first implementation what is happening is that you are defining an iterator groupWithFalse that iterates over the elements of group that have SomeProp set to false. You've iterated over groupWithFalse and set SomeProp to true for all of those elements and now when you iterate again (the call to Any produces a second iteration), every element has SomeProp set to true since you just set it to be so and so of course Any returns false (the iteration produces the empty collection now).

To elaborate a little bit, basically when you say

IEnumerable<SomeClass> groupWithFalse =(from SomeClass gr in group
                                        where gr.SomeProp== false
                                        select gr);

you are defining an object (groupWithFalse) that captures the logic "give me a sequence of the elements in group that have SomeProp set to false." Explicitly, this object is not actually a collection or sequence of those elements for which SomeProp is set to false. The first time that you iterate over this object (foreach), this rule will produce elements for which SomeProp is false (if there are any in group). But when you iterate a second time (Any), this rule will not produce any elements because you've modified the collection group from which this rule draws its elements from.

In the second iteration you are checking if the List<SomeClass> named groupWithFalse is the empty collection which it is not because you've added elements to it in the foreach loop that immediately precedes it.

Here's how I would write your code:

public static void SomeMethod (List<SomeClass> group) {
    IEnumerable<SomeClass> groupWithFalse =(from SomeClass gr in group
                                            where gr.SomeProp== false
                                            select gr);
    if(groupWithFalse.Any()) {
        foreach (SomeClass grFalse in groupWithFalse) {
            grFalse.Save();
        }

        // do some stuff
    }
}

Now the code reads like "Get the elements of group for which SomeProp is false. If there are Any, Save them and then do some stuff."

Upvotes: 0

Adam Maras
Adam Maras

Reputation: 26853

Your problem is occurring because of deferred execution. In your first example, groupWithFalse doesn't represent a list of objects where SomeProp is false, it refers to a query that can evaluate to said list.

If you want your list to stay put during your entire function, simply add .ToList() on the end of your LINQ query like so:

IEnumerable<SomeClass> groupWithFalse =(from SomeClass gr in group
                                        where gr.SomeProp == false
                                        select gr).ToList();
//                                                ^^^^^^^^^ 

This will cause the query to execute immediately and return its results into a List<SomeClass> whose contents won't change if you modify the objects within it.

Upvotes: 2

Euphoric
Euphoric

Reputation: 12849

Your problem lies in Lazy Evalution of LINQ. In first example, the LINQ query is actualy run TWICE. First for enumerating for calling your Save() method, and second to get Any().

As general rule while working with LINQ, put ToList() after each and every query.

Upvotes: 0

SLaks
SLaks

Reputation: 887453

Running a LINQ query does not store the results.
Instead, it will re-execute the query each time you enumerate it.

After calling Save(), the query will be empty, since none of the items meet the where clause.

Change it to

var unsaved = group.Where(g => !g.SomeProp).ToList();

Calling ToList() will store the results in a List<T>; this avoids re-executing the query.

Upvotes: 3

Related Questions