Introduction
As developers, haven't we all wondered whether to use a FirstOrDefault or a SingleOrDefault?
If you're a young developer, you've probably dismissed this question out of hand, telling yourself that in the vast majority of cases, good old FirstOrDefault does the job, because "who can do more, can do less".
On the other hand, some Developers, through an automatism acquired during their training or through personal conviction, use SingleOrDefault when the element to be identified is actually supposed to be unique.
But what is it really?
A little semantics
FirstOrDefault
Linq method which :
Returns the first element of a sequence, or a default value if the sequence contains no elements.
Source: Microsoft Learn
In short, this method allows you to browse a collection and extract the first element corresponding to the specified predicate (or the first entry if no predicate is specified), or a default value if no match with the predicate is identified.
SingleOrDefault
Linq method which :
Returns a specific single element of a sequence, or a default value if the element cannot be found.
Source: Microsoft Learn
In short, this method allows you to browse a collection and extract the single element corresponding to the specified predicate, or a default value if no match with the predicate is identified.
In practice, what's the difference?
General operation
Theoretically, SingleOrDefault would be preferable in cases where it's certain that the predicate being searched for is unique in the collection being browsed.
To ensure this, the method will scan the entire collection, even if the predicate is identified during processing.
On the other hand, the FirstOrDefault stops its action as soon as it identifies the search object.
So what happens if there is more than one match in the collection?
In the case of SingleOrDefault, it's very simple: an Exception is thrown because this situation is not supposed to occur.
As far as FirstOrDefault is concerned, it doesn't matter if there are several matches: as soon as it encounters one, it returns it.
A picture worth a thousand words.
FirstOrDefault :
SingleOrDefault :
It's essential to bear in mind that there is a risk associated with the use of SingleOrDefault, depending on the elements present in the source collection. So it's best to understand its use and plan for it in your code to ensure robustness.
"Yes, but Jamy, if the Single is proposed it's because it should be more efficient/optimized when used under the intended conditions, right?"
In reality, it's not quite that simple...
Perfomances
As mentioned above, in essence, SingleOrDefault scans the entire collection before returning a result... Or an Exception.
FirstOrDefault doesn't bother.
What does this mean in terms of processing times and memory usage?
Is this negligible, or should it be taken into account during development?
Let's take a look at the Benchmark*:
Protocol
- We're going to create collections of different sizes (100, 10,000, 1,000,000) => Haystacks.
- For each collection, we'll insert a single element, which will be the one targeted by our predicate => The needle.
- For each haystack, the needle is inserted at different positions (first, middle, last).
- We'll then measure the time and memory used to extract the needle from the various hay bales using FirstOrDefault and SingleOrDefault.
Results
- Position: 0 (first entry in collection), 0.5 (middle), 1 (last entry in collection)
- Size: Collection size
- Mean: Treatment duration
- Allocated: Memory allocated for processing
Comments
As we can see from the table, in almost all cases, FirstOrDefault wins out in terms of execution times.
The processing time is roughly the same when the element to be identified is the last in the collection. This makes sense, as it's the only case in which the FirstOrDefault has to go through the entire collection.
In fact, as the size of the collection increases, so does the difference in processing time between the two methods.
With regard to memory consumption, we can see that the two processes are similar, except when the size of the collection is very large (1,000,000 in our test). In this case, SingleOrDefault consumes more memory than its counterpart.
Conclusion
In conclusion, the penalty is quite clear-cut. Apart from one specific case, FirstOrDefault is totally advantageous in terms of execution time and resource savings.
On the other hand, the risk of SingleOrDefault's Exception being raised seems to make its "rival" the default choice.
However, if any of you have had experiences or situations that suggest the use of SingleOrDefault rather than FirstOrDefault, don't hesitate to share them with us.
It's also important to bear in mind that .Net Core is constantly evolving. Linq queries, in particular, have undergone major changes over the last few versions. It is therefore possible that some of these points will need to be updated in the future.
If you have any questions on this subject, please do not hesitate to contact me.
(*): BenchmarkDotNet is a .Net library (Nuget package) dedicated to performance measurement.
Michaël LESTAVEL
IS Consultant (specialized in .Net)