Tuesday, March 3, 2015

Direct casting vs. As and Linq Single, SingleOrDefault, First, FirstOrDefault

There are tons of articles on these two topics but I still see it so many times I'm putting out a quick reminder.

This:

var someVariable = someOtherVariable as SomeType;

Or this:

var someVariable = (SomeType)someOtherVariable;

I come across people doing the first when they should be direct casting (the second) all the time in code reviews.  When I come across the first the first thing I look for, somewhere very soon after in the code, is a null check.  Why? because using the syntax of the first means that you expect that someOtherVariable may be not castable to SomeType and when that happens someVariable will be set to null.  That is to say that since it may be something other than SomeType, you need to handle that case in code.

If however, someOtherVariable should always have at value that is castable to SomeType and anything else is a logic error somewhere in the code, then you should be using direst casting.  Direct casting will throw an exception right then and there if it is not SomeType and that's what you want. Why?  Because somewhere in your code you have a logic error and you want to find out about it as soon as possible.  Pretty simple to remember.

OK, what are the differences between these Linq statements:

var someVariable = someOtherVariable.First(r => r.SomeValue == "value");

var someVariable = someOtherVariable.FirstOrDefault(r => r.SomeValue == "value");

var someVariable = someOtherVariable.Single(r => r.SomeValue == "value");

var someVariable = someOtherVariable.SingleOrDefault(r => r.SomeValue == "value");

Here is my easy way to remember:

First: ( 1 .. n) It is valid to have one or more results and you want/get the first.  If there are no results that is a logic error and should throw an exception.

FirstOrDefault: ( 0 .. n) It is valid to have zero, one or more valid results and you want/get the first.  Generally if you have no valid result someVariable will be set to null, similar to the "as" syntax above.

Single: ( 1 .. 1 ) It is valid to have one and only one result.  Anything else (zero or more than one) is considered a logic error and will throw an exception.  Great for unique Ids when you know the record should be in the collection

SingleOrDefault: ( 0 .. 1): There should be zero or one result, more than one matching result will throw an exception.  Generally if you have no valid result someVariable will be set to null, similar to the "as" syntax above.  Great for unique Ids when you don't know if the record has been added to the collection and if it isn't in there, you need to add it.

I see these misused all the time too, particularly people favoring FirstOrDefault.  When I've asked them about it misuse usually comes from not understanding the statements or aversion to throwing an exception (i.e they think to themselves, but what if there are no, or many results).

The aversion to throwing an exception in these cases is wrong headed thinking.  Yes, exceptions that get to the user are bad, but logic errors can be similarly bad.  Even worse is logic errors that you don't catch immediately that lead to exceptions later on and now you have the unenviable task of trying to trace back and figure out many lines of code before your variable was set to null in a situation that never should have happened and you didn't handle.

Using the "as" syntax or FirstOrDefault out of a desire to avoid exceptions is a bad practice similar to swallowing exceptions.  Don't do it.  Fail right away and you as the developer will likely find the problem, and if you don't QA has a better chance to than if you simply don't throw and exception and keep going.  By swallow the problem by using "as" or FirstOrDefault you increases the chance that the person finding an issue will be the end user and you will likely have a harder time tracking down the problem and some egg on your face.

No comments:

Post a Comment