关于c#:如果有可能找不到该元素,我应该使用Single()还是SingleOrDefault()?

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

你更喜欢看什么?

1
2
3
4
5
6
7
8
try
{
  var item = list.Single(x => x.HasFoo);
}
catch(InvalidOperationException e)
{
  throw new InvalidOperationException("Exactly one item with foo expected, none found", e);
}

要么:

1
2
3
var item = list.SingleOrDefault(x => x.HasFoo);
if (item == null)
      throw new InvalidOperationException("Exactly one item with foo expected, none found");

这里的最佳做法是什么? 哪一个让异常更容易理解?


  • 如果需要0或1项,请使用SingleOrDefault()
  • 如果是1,而不是0或2,则使用Single()项目

还要记住,有许多可能的情况:

  • 当0或1预期时,你得0(ok)
  • 预计0或1时你得到1(ok)
  • 当预期为0或1时,你得到2或更多(错误)

和:

  • 当预期为1时,你得到0(错误)
  • 预计1时你有1(好)
  • 当预期为1时你有2个或更多(错误)

不要忘记First()FirstOrDefault()Any()


我会写:

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

如果这不返回单个项目的情况如此常见,您需要一个更友好的错误消息,那么它真的是一个例外吗?


实际上,它们是一样的。但我更喜欢第二个,因为在前两个中抛出一个异常。例外是昂贵的。


我认为写作是可以的

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

但你也可以写

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

如果您实际上不需要访问该值。


如果您总是预测列表中的一个元素,只需使用 

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

并在顶级方法中捕获异常,您将在其中记录异常的详细信息并向用户显示友好消息。

如果您有时期望0或超过1个元素,最安全的方法将是

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

我假设,验证不重要,是否存在超过1条记录。

代码项目文章LINQ:Single vs. SingleOrDefault中讨论了类似的问题


假设您询问0..1场景,我更喜欢SingleOrDefault,因为它允许您指定自己的方式来处理"无法找到"场景。

因此,使用少量语法糖的好方法是:

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

其中notFound()是:

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


在获取元素之前,我宁愿查看列表中元素的数量,而不是等待异常,然后再抛出一个新元素。

1
2
3
4
5
6
7
8
9
10
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");
}

这些建议很紧凑很好,但更好的是更明确的IMO。

(同样在你的建议中,例外不是严格有效的:当可能有多个时,他们说"没有找到")

编辑:Jeebus,为迂腐的人添加了一行来过滤列表。 (我认为这对任何人来说都是显而易见的)


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.

这是示例: -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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();

    }
 }  
}

我同意Kieren Johnstone,不要等待这个非常昂贵的例外,当你多次调用这种方法时。

你是第一个代码片段甚至更昂贵,因为你等待原始的异常,而不是给自己一个新的。