开发者

Should I use Single() or SingleOrDefault() if there is a chance that the element won't be found?

开发者 https://www.devze.com 2023-02-24 06:34 出处:网络
What would you prefer to see? try { var item = list.Single(x => x.HasFoo); } catch(InvalidOperationException e)

What would you prefer to see?

try
{
  var item = list.Single(x => x.HasFoo);
}
catch(InvalidOperationException e)
{
  throw new InvalidOperationException("Exactly one item with foo expected, none found", e);
}

Or:

var item = list.SingleOrDefault(x => x.HasFoo);
if (item == null)
      throw new InvalidOperationException("Exactly one item 开发者_开发百科with foo expected, none found");

What's the best practice here? Which one makes the exception more comprehensible?


  • Use SingleOrDefault() if 0 or 1 items are expected
  • Use Single() if 1, not 0 or 2 and more, item is expected

Also keep in mind that there are a number of possible scenarios:

  • You got 0 when 0 or 1 was expected (ok)
  • You got 1 when 0 or 1 was expected (ok)
  • You got 2 or more when 0 or 1 was expected (error)

And:

  • You got 0 when 1 was expected (error)
  • You got 1 when 1 was expected (ok)
  • You got 2 or more when 1 was expected (error)

And don't forget about First(), FirstOrDefault() and Any()


I would write:

var item = list.Single(x => x.HasFoo);

If the case where this does not return a single item is so common you need a friendlier error message, then is it really an exception at all?


Practically, they are the same. But I prefer second one since one exception is thrown while in the first two. Exceptions are expensive.


I think it's OK to write

var item = list.SingleOrDefault(x => x.HasFoo);
if (item == null) ...

but you can also write

if (list.Any(x => x.HasFoo)) ...

if you don't actually need access to the value.


If you ALWAYS EXPECT  one element in the list, just use 

var item = list.Single(x => x.HasFoo);

and catch exception at the top level method, where you will log details of exception and show friendly message to the user.

If you sometimes expect 0 or more than 1 elements, the safest method will be

var item = list.FirstOrDefault(x => x.HasFoo);
if (item == null) 
{ 
// empty list processing, not necessary throwing exception
}

I assumed, that it is not important to verify, are more than 1 record exist or not.

Similar question was discussed in Code Project article LINQ: Single vs. SingleOrDefault


I'd rather see a check to the number of elements in the list before getting the element, rather than waiting for an exception, then throwing a new one.

var listFiltered = list.Where(x => x.HasFoo).ToList();
int listSize = listFiltered.Count();
if (listSize == 0)
{
    throw new InvalidOperationException("Exactly one item with foo expected, none found");
}
else if (listSize > 1)
{
    throw new InvalidOperationException("Exactly one item with foo expected, more than one found");
}

It's nice that the suggestions are compact, but better to be more explicit IMO.

(Also in your suggestions the exceptions are not strictly valid: they say 'none found' when there could be more than one)

Edit: Jeebus, added one line to filter the list first for pedantic people. (I thought it would have been obvious for anyone)


Assuming you were asking about the 0..1 scenario, I prefer SingleOrDefault because it lets you specify your own way to handle the "nothing found" scenario.

So, a good way to do using a little syntactic sugar, would be:

// assuming list is List<Bar>();
var item = list.SingleOrDefault(x => x.HasFoo) ?? notFound<Bar>();

where notFound() is:

T notFound<T>()
{
  throw new InvalidOperationException("Exactly one item with foo expected, none found");
}


I agree with Kieren Johnstone, don't wait for the exception this is pretty costly, sure when you call this method alot of times.

You're first code snippet is even more expensive, because you wait for the original exception, and than throw yourself a new one.


Single

It returns a single specific element from a collection of elements if element match found. An exception is thrown, if none or more than one match found for that element in the collection.

SingleOrDefault

It returns a single specific element from a collection of elements if element match found. An exception is thrown, if more than one match found for that element in the collection. A default value is returned, if no match is found for that element in the collection.

here is sample example:-

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LinqSingleorSingleOrDefault
{
class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string City { get; set; }
}

public class Program
{

    static void Main(string[] args)
    {
        IList<Employee> employeeList = new List<Employee>(){
            new Employee() { Id = 10, Name = "Chris", City = "London" },
            new Employee() { Id=11, Name="Robert", City="London"},
            new Employee() { Id=12, Name="Mahesh", City="India"},
            new Employee() { Id=13, Name="Peter", City="US"},
            new Employee() { Id=14, Name="Chris", City="US"}
        };

        //Single Example

        var result1 = employeeList.Single(); 
        // this will throw an InvalidOperationException exception because more than 1 element in employeeList.

        var result2 = employeeList.Single(e => e.Id == 11);
        //exactly one element exists for Id=11

        var result3 = employeeList.Single(e => e.Name == "Chris");
        // throws an InvalidOperationException exception because of more than 1 element contain for Name=Chris

        IList<int> intList = new List<int> { 2 };
        var result4 = intList.Single(); 
        // return 2 as output because exactly 1 element exists


        //SingleOrDefault Example

        var result5 = employeeList.SingleOrDefault(e => e.Name == "Mohan");
        //return default null because not element found for specific condition.

        var result6 = employeeList.SingleOrDefault(e => e.Name == "Chris");
        // throws an exception that Sequence contains more than one matching element

        var result7 = employeeList.SingleOrDefault(e => e.Id == 12);
        //return only 1 element

        Console.ReadLine();

    }
 }   
}
0

精彩评论

暂无评论...
验证码 换一张
取 消