I’ve been comfortable using LINQ for what seems like a long time. I’m noticing that I’ve been implementing some of the lesser used functions a bit more as of late, but I’m not sure exactly when they’re appropriate. Take the following method that is a prime candidate for leveraging LINQ.
Any time you see that if inside a foreach in C#, you’ll probably want to write this differently. A year ago, I probably would have written this:
More recently, my thought process makes me want to write that same code like so:
For some reason, I like the second LINQ implementation better. It just seems to read nicer and reflect the method’s intent a bit more accurately.
I don’t have a strong preference either way, so I decided to see how different they actually are in IL:
As far as I’m concerned, these are exactly the same. I’m going to go with the second implementation, the one that uses .SkipWhile().
Oh hey, I'm a Senior Consultant for Headspring in Austin, TX. I've been working in software professionally since 2006 and I really, really love it. I'm mostly in the Microsoft world, but enjoy building computer things of all sorts (to be vague). When I'm not slinging code, I'm probably out and about slinging discs, bowling balls, or good beer with great friends.
FirstOrDefault, like many LINQ methods lets you use a test lambda directly:
list.Select(l => new DirectoryInfo(l)).FirstOrDefault(d => d.Exists)
I haven’t looked, but I suspect the IL would again be similar.
http://chrismissal.lostechies.com Chris Missal
Ahh yes, I wrote that without Visual Studio or ReSharper (and R# gives you a suggestion to “Replace with single call to FirstOrDefault(..)”. The green squiggly would have reminded me of this.
That definitely helps shift my preference in the other direction. Less code!
Chris Bristol
I’m not sure (and I’m too lazy to check), but I think the compiler will actually optimize:
items.Where(item => item.X).FirstOrDefault();
into
items.FirstOrDefault(item => item.X);
as long as the result isn’t being assigned to an expression tree.
Mihasic
AFAIK, it’s not the compiler to solve that problem and those are different implementation.
First: create Iterator wrapper depending on type of enumerable, than browse for first item.
Second: just foreach loop with condition check (actual original method).
Pierre-Alain Vigeant
There is a implementation problem with FirstOrDefault(lambda). Using a reflector tool, you can see that the Where(lambda) does a chekc on the type of collection and change its iteration method accordingly, whereas FirstOrDefault(lambda) use a simple foreach.
I don’t know the rational behind that, because I think they should be implemented the same way.
So technically, while the result is the same, they are not doing the same process, and Where(lamba).FirstOrDefault() could be faster than FirstOrDefault(lambda) depending on the type of collection.
This doesn’t mean much as you’re looking at your own IL, not SkipWhile or Where IL. Of course it looks the same, it’s just a method call.
SkipWhile and Where have different implementations and different purposes. It only works the same because you discard the rest of the enumeration taking only the first item. But again, you could accomplish the same passing the predicate directly into FirstOrDefault call, as stated by Casey and Jon.
Will Rogers
I prefer the first LINQ version. The SkipWhile version addresses the items you *don’t* want to Select, therefore requiring a second negative (d => !d.Exists) in order to resolve things.
Or, if you prefer: Of the LINQ examples, I don’t prefer the one that does not not require a double negative. :-)
http://www.daedtech.com/blog Erik Dietrich
I agree with Will, but I recognize that this is purely a subjective distinction. The task is, “given this list of potential directories, find the first one that exists on disk”. It seems a little awkward to rephrase that as “given this list of potential directories, find the first one that doesn’t not exist on disk.”
But, I suppose that’s the beauty of Linq – we get to argue over which pretty readable piece of semantics is slightly better. :)
http://chrismissal.lostechies.com Chris Missal
Lol, yes we do! :)
Daniel González García
I have to agree with some the folks that shy away from negative boolean logic.
The less code to parse and evaluate while reading it, the best.
And the second case does not click in my mind automatically, but needs second thoughts.
In my coding I favor the predicate-receiving functions of .First(), .Last(), .Single() (and their …OrDefault() counterparts) over the .Where() filtering upfront.
But, at the end of the day, what matters is that LINQ rocks and has turned around my way of thinking about loops and sequence processing.
Well yeah, function calls to different functions with the same arguments will look similar in IL. What does this prove?
Surely you want to be looking at the difference in the implementations of Enumerable.Where and Enumerable.SkipWhile! In fact though they are very similar, the TakeWhile enumerator just has an extra flag to ensure that it returns everything after the first element that didn’t match.
Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1135