Подтвердить что ты не робот

Должен ли я использовать Single() или SingleOrDefault(), если есть вероятность, что элемент не будет найден?

Что бы вы предпочли увидеть?

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

Или:

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

Какая самая лучшая практика здесь? Какой из них делает исключение более понятным?

4b9b3361

Ответ 1

  • Используйте SingleOrDefault(), если ожидается 0 или 1 элемент
  • Используйте Single(), если 1, а не 0 или 2 и более, элемент ожидается

Также имейте в виду, что существует ряд возможных сценариев:

  • Вы получили 0, когда ожидалось 0 или 1 (ok)
  • Вы получили 1, когда ожидалось 0 или 1 (ok)
  • Вы получили 2 или более, когда ожидалось 0 или 1 (ошибка)

и

  • Вы получили 0, когда ожидалось 1 (ошибка)
  • Вы получили 1, когда ожидалось 1 (ok)
  • Вы получили 2 или более, когда ожидалось 1 (ошибка)

И не забывайте о First(), FirstOrDefault() и Any()

Ответ 2

Я бы написал:

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

Если случай, когда это не возвращает один элемент, настолько распространен, что вам нужно более дружественное сообщение об ошибке, то действительно ли это исключение?

Ответ 3

Практически, они одинаковы. Но я предпочитаю второй, поскольку одно исключение выбрано в первых двух. Исключения стоят дорого.

Ответ 4

Я думаю, что нормально писать

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

но вы также можете написать

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

если вам действительно не нужен доступ к значению.

Ответ 5

Я предпочел бы проверить количество элементов в списке перед тем, как получить элемент, а не ждать исключения, а затем выбросить новый.

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.

(Также в ваших предложениях исключения не являются строго верными: они говорят "нет", если их может быть несколько)

Edit: Jeebus, добавил одну строку, чтобы сначала отфильтровать список для педантичных людей. (Я думал, что это было бы очевидно для всех)

Ответ 6

Если вы ВСЕГДА ОЖИДИТЕСЬ один элемент в списке, просто используйте

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

и исключение catch в методе верхнего уровня, где вы будете регистрировать сведения об исключении и показывать дружественное сообщение пользователю.

Если вы иногда ожидаете 0 или более 1 элемента, самым безопасным способом будет

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

Я предположил, что не важно проверять, существует ли более 1 записи или нет.

Аналогичный вопрос обсуждался в статье Code Project LINQ: Single vs. SingleOrDefault

Ответ 7

Предполагая, что вы спрашивали о сценарии 0..1, я предпочитаю SingleOrDefault, потому что он позволяет вам указать свой собственный способ обработки сценария "ничего не найден".

Итак, хороший способ сделать это с использованием небольшого синтаксического сахара:

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

где notFound():

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

Ответ 8

Я согласен с Kieren Johnstone, не ждите исключения, это довольно дорого, конечно, когда вы вызываете этот метод много раз.

Вы первый фрагмент кода еще дороже, потому что вы ждете оригинального исключения, а не бросаете себе новый.

Ответ 9

Single

Он возвращает один конкретный элемент из набора элементов, если найдено совпадение элементов. Исключение выдается, если не указано ни одного или более одного найдено совпадение для этого элемента в коллекции.

SingleOrDefault

Он возвращает один конкретный элемент из набора элементов, если найдено совпадение элементов. Исключение выбрано, если более одного совпадения найденный для этого элемента в коллекции. Возвращается значение по умолчанию, если для этого элемента в коллекции не найдено совпадения.

Вот пример: -

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();

    }
 }   
}