Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

LINQ and Lazy, Frenemies

4.88/5 (6 votes)
3 Oct 2013CPOL2 min read 7.2K  
LINQ is awesome and Lazy is handy as heck, but using them together requires some thought.

LINQ is great, isn’t it? I freakin’ love it. Along with LINQ, came our also-now-familiar friend, IEnumerable<T>. Whenever I’m passing around a collection now, I pass around IEnumerable. It’s flexible and can always be changed in to whatever I need at the time (List<T>, Array, etc) as well as supports all the awesome LINQ stuff that we all know and love.

Y’know what else I love? Lazy<T>. Talk about speeding up your load times. If you’re not using it, you should be.

But here is where it gets tricky. What if I have a collection of Lazy objects? Use case would be something like in my app I have all the cards in a deck. I don’t need/want to spin through and create a representation of every single card at start up, let’s just fetch them as they’re needed, and cache the resulting object (a perfect job for Lazy<T>).

The deck would, naturally, be an IEnumerable<T>, right? Sweet. So let’s create a faux unit test to somewhat show what I’m talking about:

C#
1: [TestClass]
2: public class UnitTest1
3: {
4:     class MyObject
5:     {
6:         public MyObject()
7:         {
8:  
9:         }
10:     }
11:  
12:     IEnumerable<Lazy<MyObject>> _myObjects = new Lazy<MyObject>[0];
13:  
14:     [TestInitialize]
15:     public void TestInitializer()
16:     {
17:         _myObjects = Enumerable.Range(0, 5).Select(
                i => new Lazy<MyObject>(() => new MyObject()));
18:     }
19:  
20:     [TestMethod]
21:     public void ProveLaziness()
22:     {
23:         foreach (var o in _myObjects)
24:         {
25:             Assert.IsFalse(o.IsValueCreated, "Item has a value, shouldn't right now!");
26:         }
27:  
28:         foreach (var o in _myObjects)
29:         {
30:             var val = o.Value;
31:         }
32:  
33:         foreach (var o in _myObjects)
34:         {
35:             Assert.IsTrue(o.IsValueCreated, 
                 "Item doesn't have a value, should cuz we went and forced creation of it!");
36:         }
37:     }

And give ‘er a go.

Image 1

WTF? I created my collection of Lazies, looped through each one and forced creation of the value, but the IsValueCreated came back false? That means the next time I ask for any item in the collection, or every item, they’ll all be created AGAIN? That’s not what Lazy does!

But it is what LINQ does.

LINQ, if you recall is a deferred execution paradigm. When you define a LINQ expression, you are defining a query to be run when it’s needed – not immediately. So in my example above, the _myObjects variable is a query, not an actual collection. When I enumerate (via foreach) _myObjects, the query is run again. Which, as you can see, re-creates the collection of Lazy<MyObject> instances. That’s why the 3rd loop (line 33) fails.

So we still want all the benefits of Lazy, but we’d also like to be able to use LINQ to create, filter, sort, etc any of our Lazy objects… what to do… The solution is force execution of the LINQ query when you create the collection. You can do this any number of ways, but my favorite looks something like this:

C#
1: [TestMethod]
2: public void HowToFix()
3: {
4:     _myObjects = _myObjects.ToArray(); 
   // force evaluation of the IEnumerable, save THAT evaluation of it
5:  
6:     ProveLaziness();
7: }

By running .ToArray(), the LINQ query has to run NOW so that I can get an Array back. This effectively creates an actual array of actual Lazy<T>’s whose states will be maintained.

And it works!

Image 2

LINQ is awesome and Lazy<T> is handy as heck, but using them together requires some thought. Be sure to comment your code when you’re working with this or invariably you’ll have somebody lop off the .ToArray() there, and kill all your performance gain and state.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)