|
Imagine if you will that you have a variable of type List<Customer> and you are doing some querying on this variable. Everyone, no matter what application they're writing, will at some point need to write code that retrieves an element from a list. Contains and IndexOf do a pretty good job, but they only work in limited fashion. What if you need to retrieve the customer from the list that has a specific social security number, but your object is not using SSN in it's CompareTo implementation? In the "old days" without LINQ, you'd have to write code to troll through the list and pick up the target customer if you found a match on SSN. Today, you can write a LINQ query to obtain that customer.
The problem comes from the fact that LINQ will not return null when you use the First() method. Take a look at a sample query, one that would be perfectly acceptable to anybody who has a background in SQL statements:
var q = from Customer cust in custList where cust.SSN="555-55-5555" select cust;
This is great, but you're going to get an array, not a single customer. You know ahead of time that you're only going to get a single customer from thsi query, so you think - maybe I'll use the "First()" method like this:
Customer cust = (from Customer cust in custList where cust.SSN="555-55-5555" select cust).First();
At first glance this looks pretty good. But, what happens if there is no customer in that list with that SSN? Personally, I would like cust to be null. But what really happens is LINQ throws an InvalidOperation exception and completely ruins your code and your execution context. So, I figure, maybe some refactoring is in order:
Customer cust = custList.First( c => c.SSN = "555-55-5555" );
That's got to work, right? Nope. You still get an invalid operation when the filter function doesn't match. To get around this, I wrote a language extension called SafeFirst that wraps both forms of First<T>() in checks for a positive result count:
static class ThisShouldBeUnnecessary
{
public static T SafeFirst<T>(this IEnumerable<T> input, Func<T,bool> func)
{
IEnumerable<T> results = input.Where(func);
if (results.Count() > 0)
return results.First();
else
return default(T);
}
public static T SafeFirst<T>(this IEnumerable<T> input)
{
if (input.Count() > 0)
return input.First();
else
return default(T);
}
}
And now just replace your First() method calls with SafeFirst and you'll get null as you would expect. In fact, you get the default value for the type "T" so that would be a 0 for an int, null for any pointer type, DateTime.MinValue for a date, etc.
This kind of workaround should not be necessary, and if anybody else has a more elegant way around this particular problem, I'd love to see it.
This is exactly why reliance on Intellisense has made me such a lazy
developer. I don't see that in my intellisense dropdown on an IEnumerable,
but I do see First() ... I wonder why? I'll have to check it out and, if it
works, that fixes my problem without me having to create a new language
extension and re-invent the wheel. Thanks for the heads-up!
I'm pretty sure Single(), First() and the like throw exceptions because a
couple things can go wrong in a LINQ query, so you should 'try' the whole
shebang. The exception caught may or may not have something to do with
Single() for First().