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

Рекомендации по работе с операторами LINQ, которые приводят к пустым последовательностям и тому подобное?

... Я немного смущен или не уверен, как справляться с ошибками, возникающими из операторов LINQ. Мне просто нравится иметь возможность вытаскивать один или несколько элементов из коллекции, основываясь на некоторых критериях... с одной строкой кода. Это довольно удивительно.

Но где я разорван с обработкой ошибок или проверкой граничных условий. Если я хочу получить элемент, используя First(), и ни один элемент не удовлетворяет моему запросу, генерируется исключение. Это немного облом, потому что теперь я должен обернуть каждую инструкцию LINQ отдельным блоком try/catch. Для меня код начинает выглядеть немного беспорядочным со всем этим, тем более, что мне приходится объявлять переменные за пределами блока try/catch, поэтому я могу использовать их (нулевые) значения позже (которые были установлены в null в блок catch).

Кто-нибудь здесь понимает мое затруднительное положение? Если мне придется обернуть каждую инструкцию LINQ в блоках try/catch, я буду, потому что это все еще намного лучше, чем писать всевозможные циклы для выполнения того же самого. Но должен быть лучший способ, не так ли?:) Я бы хотел услышать, что здесь делают все остальные в этой ситуации.

** ОБНОВЛЕНИЕ **

Спасибо за ответы, ребята, они очень помогли. Еще одна вещь, которую я также собирался воспитывать по "односторонности" LINQ, заключается в том, что если я хочу получить некоторое .some_value.some_value.some_other_value, если я приму подход, когда я должен проверить Nullable, Сначала я должен сделать это из самого базового запроса LINQ, затем я могу запросить свойство результата, которое я ищу. Думаю, там не обойтись без этого?

4b9b3361

Ответ 1

Используйте сначала, когда вы знаете, что в коллекции есть один или несколько элементов. Используйте Single, когда вы знаете, что в коллекции есть только один элемент. Если вы этого не знаете, не используйте эти методы. Используйте методы, которые делают что-то еще, например FirstOrDefault(), SingleOrDefault() и т.д.

Вы могли бы, например, сказать:

int? first = sequence.Any() ? (int?) sequence.First() : (int?) null;

что намного меньше, чем

int? first = null;
try { first = sequence.First(); } catch { }

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

Продолжая наш пример, предположим, что у вас есть последовательность целых чисел и вы хотите получить первый элемент, или, если таковой нет, верните null. Для этого не существует встроенного оператора последовательности, но его легко написать:

public static int? FirstOrNull(this IEnumerable<int> sequence)
{
    foreach(int item in sequence)
        return item;
    return null;
}

или даже лучше:

public static T? FirstOrNull<T>(this IEnumerable<T> sequence) where T : struct
{
    foreach(T item in sequence)
        return item;
    return null;
}

или это:

struct Maybe<T>
{
    public T Item { get; private set; }
    public bool Valid { get; private set; }
    public Maybe(T item) : this() 
    { this.Item = item; this.Valid = true; }
}

public static Maybe<T> MyFirst<T>(this IEnumerable<T> sequence) 
{
    foreach(T item in sequence)
        return new Maybe(item);
    return default(Maybe<T>);
}
...
var first = sequence.MyFirst();
if (first.Valid) Console.WriteLine(first.Item);

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

UPDATE:

Дэйв спрашивает, как сделать FirstOrNull, который берет предикат. Достаточно легко. Вы можете сделать это следующим образом:

public static T? FirstOrNull<T>(this IEnumerable<T> sequence, Func<T, bool> predicate) where T : struct
{
    foreach(T item in sequence)
        if (predicate(item)) return item;
    return null;
}

Или как это

public static T? FirstOrNull<T>(this IEnumerable<T> sequence, Func<T, bool> predicate) where T : struct
{
    foreach(T item in sequence.Where(predicate))
        return item;
    return null;
}

Или, даже не беспокойтесь:

var first = sequence.Where(x=>whatever).FirstOrNull();

Нет причин, по которым предикат должен пройти FirstOrNull. Мы предоставляем First(), который использует предикат как удобство, так что вам не нужно вводить дополнительные "Where".

ОБНОВЛЕНИЕ: Дэйв задает еще один следующий вопрос, который, я думаю, может быть "что, если я хочу сказать последовательность .FirstOrNull(). Frob().Bhah(). Что бы ни было(), но любой из них по линии return null?"

Мы рассмотрели вопрос о добавлении к С# оператора распространения с нулевым распространением, предварительно обозначенного как ?., то есть вы могли бы сказать

x = a?.b?.c?.d;

и если a, b или c произведут нуль, то результатом будет присвоение значения null переменной x.

Очевидно, мы фактически не реализовали его для С# 4.0. Это возможный рабочий элемент для гипотетических будущих версий языка... ОБНОВЛЕНИЕ "Оператор Elvis" добавлен в С# 6.0, yay!

Обратите внимание, что С# имеет нулевой коалесцирующий оператор:

(sequence.FirstOrNull()?? GetDefault()). Frob(). Blah(). Whatever()

означает "Если FirstOrNull возвращает non-null, используйте его как приемник Frob, иначе вызовите GetDefault и используйте это как приемник". Альтернативный подход будет повторен, напишите свой собственный:

public static T FirstOrLazy<T>(this IEnumerable<T> sequence, Func<T> lazy) 
{
    foreach(T item in sequence)
        return item;
    return lazy();
}

sequence.FirstOrLazy(()=>GetDefault()).Frob().Blah().Whatever();

Теперь вы получаете первый элемент, если он есть, или результат вызова GetDefault(), если этого не происходит.

Ответ 2

Используйте FirstOrDefault, а затем проверьте null.

Ответ 3

Кто-нибудь здесь понимает мое затруднительное положение?

Не действительно, если вы замените First() на FirstOrDefault(), ваши блоки try/catch могут быть заменены операторами if(...) или стратегически используемыми операторами && или ||.

Ответ 4

Операторы FirstOrDefault и SingleOrDefault решают вашу проблему.

Аналогичная проблема, с которой я столкнулся, - это когда коллекция содержит коллекцию; вложенный список. В этом случае я часто использую нулевой оператор коалесценции, чтобы разрешить однострочный поиск через вложенный список. Самый тривиальный случай выглядит так:

var nestedList = new List<List<int>>();
int? first = (nestedList.FirstOrDefault() ?? new List<int>).FirstOrDefault();

Итак, если внешний список пуст, возвращается новый пустой список, который просто позволяет окончательному FirstOrDefault возвращать null.

Ответ 5

В дополнение к реализациям Eric Lippert FirstOrNull здесь приведена версия SingleOrNull для типов значений.

    /*
     * This SingleOrNull implementation is heavily based on the standard
     * Single/SingleOrDefault methods, retrieved from the reference
     * source codebase on Thu May 7, 2015.
     * http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs
     *
     * In case it isn't clear, the first part is merely an opportunistic
     * optimization for sources that are actually lists, and which thus
     * expose a precomputed count.  Using a count is faster since we
     * only have to read 0-1 elements.  In contrast, the fallback must
     * read 1-2 elements.
     */
    public static TSource? SingleOrNull<TSource>(
        this IEnumerable<TSource> source)
        where TSource : struct
    {
        if (source == null) throw new ArgumentNullException("source");
        var list = source as IList<TSource>;
        if (list != null)
        {
            switch (list.Count)
            {
                case 0: return null;
                case 1: return list[0];
            }
        }
        else
        {
            using (var e = source.GetEnumerator())
            {
                if (!e.MoveNext()) return null;
                var result = e.Current;
                if (!e.MoveNext()) return result;
            }
        }
        return null;
    }

И вот несколько тестов, заброшенных для хорошей оценки.