Должен ли я использовать 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");
Какая самая лучшая практика здесь? Какой из них делает исключение более понятным?
Ответы
Ответ 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();
}
}
}