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

Почему Single() не возвращается напрямую, когда найдено более одного элемента?

Я нашел (примерно) этот код в Enumerable.Single при проверке его с помощью некоторого декомпилятора:

foreach (TSource current in source)
{
    if (predicate(current))
    {
        result = current;
        num += 1L;
    }
}

if (num > 1L)
{
     throw Error.MoreThanOneMatch();
}

Как вы можете видеть, он перебирает все элементы перед бросанием. Почему он не разбивается, когда num > 1?

4b9b3361

Ответ 1

Согласитесь, что это будет лучше с точки зрения производительности (EDIT: если мы ожидаем, что более одного элемента соответствует нашему предикату, чего мы не должны делать):

foreach (TSource current in source)
{
    if (predicate(current))
    {
        result = current;
        num += 1L;

        if (num > 1L)
            throw Error.MoreThanOneMatch();
    }
}

if (num == 0L)
   throw Error.NoMatch();

return local;

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

switch((int)num)
{
   case 0: throw Error.NoMatch();
   case 1: return local;
   default:
       throw Error.MoreThanOneMatch();    
}

Что касается проблем с производительностью, я думаю, что предполагается, что Single следует вызывать, когда вы действительно ожидаете одного результата. Нулевые или более результаты - это исключительный путь, который не должен происходить часто (как любое исключение). Таким образом, это скорее ваша ошибка логики программы, если источник содержит много элементов, соответствующих предикату.

Ответ 2

Под Single подразумевается, ровно один, а не один, а также не более одного.
Он перечисляет все элементы, чтобы убедиться, что это только один.
Он генерирует исключение, если нет ни одного, ни другого. SingleOrDefault вместо этого выбрасывает, если их больше, но возвращает default(T)/null, если их нет.

То, что вы ищете, это FirstOrDefault, который разбивает перечисление, если он нашел первый, соответствующий предикату. First вместо этого выбрасывает, если его нет, а также ломается (возвращается непосредственно) из него foreach, если он нашел первый.

Источник FirstOrDefault

foreach (TSource current in source)
{
    if (predicate(current))
    {
        return current;
    }
}
return default(TSource);

В то время как первый источник вместо возврата по умолчанию

throw Error.NoMatch();