На самом деле я бы сделал это так
public static T SingleOrNew<T>(this IEnumerable<T> query, Func<T> createNew)
{
using (var enumerator = query.GetEnumerator())
{
if (enumerator.MoveNext() && !enumerator.MoveNext())
{
return enumerator.Current;
}
else
{
return createNew();
}
}
}
Мне не нравится тестировать сценарий, ловя исключение.
Пояснение:
Прежде всего, я получаю перечислитель для перечисляемого. Перечислитель - это объект, имеющий метод MoveNext
и свойство Current
. Метод MoveNext
устанавливает Current
на следующее значение в перечислении (если оно есть) и возвращает значение true, если было другое значение для перемещения, и значение false, если его не было. Кроме того, перечислитель обернут внутри использования статистики, чтобы убедиться, что он утилизируется, как только мы закончим с ним.
Итак, после того, как я получу счетчик, я звоню MoveNext
. Если это возвращает false, тогда перечислимое значение было пустым, и мы сразу перейдем к оператору else (минуя второй метод MoveNext из-за оценки короткого замыкания) и вернем результат createNew
. Если тот первый вызов возвращает true, тогда нам нужно снова вызвать MoveNext
, чтобы убедиться, что второго значения нет (потому что мы хотим, чтобы было значение Single). Если второй вызов вернет true, он снова перейдет к оператору else и вернет результат createNew
. Теперь, если Enumerable имеет только одно значение, тогда первый вызов MoveNext
вернет true, а второй вернет false, и мы вернем значение Current
, которое было установлено при первом вызове MoveNext
.
Вы можете изменить это на FirstOrNew
, удалив второй вызов MoveNext
.