12

In what cases is each solution preferred over the other?

Example 1:

if (personList.Any(x => x.Name == "Fox Mulder"))
{
  this.Person = personList.Single(x => x.Name == "Fox Mulder");
}

Example 2:

var mulder = personList.SingleOrDefault(x => x.Name == "Fox Mulder");

if (mulder != null)
{
  this.Person = mulder;
}
user1172282
  • 527
  • 1
  • 9
  • 19
  • 2
    I _imagine_ (although I have not done a benchmark) that `SingleOrDefault` is more performant than `Any`. When it comes to Linq, there is often more than one way to do something. I would pick the option that makes the most sense to you. – Alex Booker Aug 06 '15 at 18:08
  • 1
    Calling Any then Single accesses the personList twice where as SingleOrDefault only once. Performance wise, probably calling the latter would be best (although I've done no testing on this!) – Ric Aug 06 '15 at 18:12

3 Answers3

12

Both Single and SingleOrDefault will enumerate the collection beyond the first matching result to verify that there is exactly one element matching the criteria, stopping at either the next match or the end of the collection. The first example will be slightly slower, since the Any call will enumerate enough of the collection (possibly all of it) to determine whether any elements meet the criteria, stopping at either the first match or the end of the collection.

There is one other critical difference: the first example could throw an exception. Single will return the matching element if there is exactly one, and throw an exception otherwise. Checking with Any does not verify this; it only verifies that there is at least one.

Based one these two reasons (primarily/especially the second reason), the SingleOrDefault approach is preferable here.


So, there are three cases here.

Case 1: No items match the condition

Option 1: .Any enumerates the entire set and returns false; .Single never executes.

Option 2: .SingleOrDefault enumerates the entire set and returns null.

Options essentially equivalent.

Case 2: Exactly one item matches the condition

Option 1: Any enumerates enough of the set to find the single match (could be the first item, could be the entire set). Next, Single enumerates the entire set to find that one item and confirm that no others match the condition.

Option 2: SingleOrDefault enumerates the entire set, returns the only match.

In this case, option 2 is better (exactly one iteration, compared to (1, 2] iterations)

Case 3: More than one element matches the condition

Option 1: Any enumerates enough to find the first match. Single enumerates enough to find the second match, throws exception.

Option 2: SingleOrDefault enumerates enough to find the second match, throws exception.

Both throw exceptions, but option 2 gets there more quickly.

yoozer8
  • 7,361
  • 7
  • 58
  • 93
  • 2
    `Single` and `SingleOrDefault` will not enumerate the entire collection - stopping at the second element, if found, is sufficient. – Jacob Krall Aug 06 '15 at 18:16
  • 3
    @Jim According to MSDN SingleOrDefault() also throws an exception if there is more than one resulting element: https://msdn.microsoft.com/en-us/library/vstudio/bb342451(v=vs.100).aspx – user1172282 Aug 06 '15 at 18:18
  • @JacobKrall thanks, I hadn't considered that. I've updated the first paragraph to reflect – yoozer8 Aug 06 '15 at 18:19
  • @Jim Why not use FirstOrDefault() then? – Engin Aug 06 '15 at 18:53
  • @JacobKrall with linq to objects the current implementation keeps going in this case. I've submitted a pull request to .Net Core moving to the sort of behaviour you describe, but it hasn't been decided whether the backwards compatibility issue of that is too significant. – Jon Hanna Aug 06 '15 at 19:35
  • @JonHanna: wow, I would not have suspected that. where is your PR? – Jacob Krall Aug 06 '15 at 19:47
  • @JacobKrall https://github.com/dotnet/corefx/pull/2350 though when I've a chance I need to merge in master again because some other performance improvements I made in another PR got accepted so it should be updated accordingly. https://github.com/dotnet/corefx/issues/2349 is the related issue. – Jon Hanna Aug 06 '15 at 20:48
  • @user1172282 I've updated to cover all three cases (no matches, one match, more than one) – yoozer8 Aug 06 '15 at 21:14
0

Extrapolating from the is v. as guidelines:

See below, from Casting vs using the 'as' keyword in the CLR:

// Bad code - checks type twice for no reason
if (randomObject is TargetType)
{
    TargetType foo = (TargetType) randomObject;
    // Do something with foo
}

By using Any and Single, you're also checking the list twice. And the same logic would seem to apply: Not only is this checking twice, but it may be checking different things, i.e., in a multi-threaded application the list could be different between the check and the assignment. In extreme cases the item found with Any might no longer exist when the call to Single is made.

Using this logic, I would go favor example two in all cases until given proof otherwise.

Community
  • 1
  • 1
jacoblambert
  • 787
  • 1
  • 11
  • 18
0

Option 3:

this.Person = personList.FirstOrDefault(x => x.Name == "Fox Mulder");

if using Entity Framework and name is the primary key, then:

this.person = db.personList.Find("Fox Mulder");

Both of these work if this.Person is null coming in, or if the person isn't found, you expect this.Person to be overwritten with null. FirstOrDefault is the fastest since it will stop at the first record that matches instead of iterating through the entire collection, but it won't throw an exception if multiple are found. The entity framework solution is even better in that case because Find will use the EF cache, possibly not even having to hit the data source at all.

Robert McKee
  • 21,305
  • 1
  • 43
  • 57