Reputation: 839
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
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
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
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
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
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