Другой взгляд на FirstOrDefault - PullRequest
10 голосов
/ 10 ноября 2009

Метод расширения IEnumerable FirstOrDefault работает не совсем так, как я хотел, поэтому я создал FirstOrValue Это хороший способ сделать это или есть лучший способ?

public static T FirstOrValue<T>(this IEnumerable<T> source, Func<T, bool> predicate, T value)
{
    T first = source.FirstOrDefault(predicate);
    return Equals(first, default(T)) ? value : first;
}

Ответы [ 4 ]

42 голосов
/ 10 ноября 2009

Ваш код, вероятно, неверен; Вы, вероятно, не рассмотрели все случаи.

Конечно, мы не можем знать, является ли какой-либо код правильным или неправильным, пока у нас нет спецификации. Итак, начните с написания однострочной спецификации:

"FirstOrValue<T> принимает последовательность T, предикат и значение T и возвращает либо первый элемент в последовательности, который соответствует предикату, если он есть, либо, если его нет, указанное значение . "

Ваша попытка на самом деле реализовать эту спецификацию? Конечно, нет! Проверьте это:

int x = FirstOrValue<int>( new[] { -2, 0, 1 }, y=>y*y==y, -1);

это возвращает -1. Правильный ответ согласно спецификации равен 0. Первый элемент, который соответствует предикату, равен нулю, поэтому его следует вернуть.

Правильная реализация спецификации будет выглядеть так:

public static T FirstOrValue<T>(this IEnumerable<T> sequence, Func<T, bool> predicate, T value)
{
    if (sequence == null) throw new ArgumentNullException("sequence");
    if (predicate == null) throw new ArgumentNullException("predicate");
    foreach(T item in sequence)
        if (predicate(item)) return item;
    return value;
}

Всегда сначала пишите спецификацию, даже если это всего лишь одно предложение.

5 голосов
/ 10 ноября 2009

default(T) вернет null по умолчанию для ссылочных типов.

Я бы сделал это

public static T FirstOrValue<T>(this IEnumerable<T> source, Func<T, bool> predicate, T value)
{
    T first = source.FirstOrDefault(predicate);
    return first ?? value;
}
0 голосов
/ 27 апреля 2015

Поскольку это перегрузка, стоит упомянуть версию без предиката.

public static T FirstOrValue<T>(this IEnumerable<T> sequence, T value)
{
    if (sequence == null) throw new ArgumentNullException("sequence");
    foreach(T item in sequence) 
        return item; 
    return value;
}
0 голосов
/ 10 ноября 2009

Мне кажется разумным, если вы хотите настроить читабельность вместо использования DefaultIfEmpty.

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

public static T FirstOrValue<T>(this IEnumerable<T> source, Func<T, bool> predicate, Func<T> getValue)
{
    T first = source.FirstOrDefault(predicate);
    return Equals(first, default(T)) ? getValue() : first;
}
...