How to use LINQ to select object with minimum or maximum property value

People.Aggregate((curMin, x) => (curMin == null || (x.DateOfBirth ?? DateTime.MaxValue) <
    curMin.DateOfBirth ? x : curMin))

Unfortunately there isn't a built-in method to do this, but it's easy enough to implement for yourself. Here are the guts of it:

public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> selector)
{
    return source.MinBy(selector, null);
}

public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> selector, IComparer<TKey> comparer)
{
    if (source == null) throw new ArgumentNullException("source");
    if (selector == null) throw new ArgumentNullException("selector");
    comparer ??= Comparer<TKey>.Default;

    using (var sourceIterator = source.GetEnumerator())
    {
        if (!sourceIterator.MoveNext())
        {
            throw new InvalidOperationException("Sequence contains no elements");
        }
        var min = sourceIterator.Current;
        var minKey = selector(min);
        while (sourceIterator.MoveNext())
        {
            var candidate = sourceIterator.Current;
            var candidateProjected = selector(candidate);
            if (comparer.Compare(candidateProjected, minKey) < 0)
            {
                min = candidate;
                minKey = candidateProjected;
            }
        }
        return min;
    }
}

Example usage:

var firstBorn = People.MinBy(p => p.DateOfBirth ?? DateTime.MaxValue);

Note that this will throw an exception if the sequence is empty, and will return the first element with the minimal value if there's more than one.

Alternatively, you can use the implementation we've got in MoreLINQ, in MinBy.cs. (There's a corresponding MaxBy, of course.)

Install via package manager console:

PM> Install-Package morelinq


NOTE: I include this answer for completeness since the OP didn't mention what the data source is and we shouldn't make any assumptions.

This query gives the correct answer, but could be slower since it might have to sort all the items in People, depending on what data structure People is:

var oldest = People.OrderBy(p => p.DateOfBirth ?? DateTime.MaxValue).First();

UPDATE: Actually I shouldn't call this solution "naive", but the user does need to know what he is querying against. This solution's "slowness" depends on the underlying data. If this is a array or List<T>, then LINQ to Objects has no choice but to sort the entire collection first before selecting the first item. In this case it will be slower than the other solution suggested. However, if this is a LINQ to SQL table and DateOfBirth is an indexed column, then SQL Server will use the index instead of sorting all the rows. Other custom IEnumerable<T> implementations could also make use of indexes (see i4o: Indexed LINQ, or the object database db4o) and make this solution faster than Aggregate() or MaxBy()/MinBy() which need to iterate the whole collection once. In fact, LINQ to Objects could have (in theory) made special cases in OrderBy() for sorted collections like SortedList<T>, but it doesn't, as far as I know.

Tags:

C#

.Net

Linq