Finding one element

LINQ's First, Last, Single, ElementAt, Min, Max and Average all share one problem: there may be nothing to return. The BCL answers this in two ways. The plain methods throw an InvalidOperationException, and the …OrDefault variants return default(T), which for a List<int> is 0, indistinguishable from a real element with the value 0, and for a reference type is a null you have to remember to check.

Funcky adds an …OrNone variant for each of them that returns an Option<T>. Empty input is None, and the value is only accessible once you have said what to do when there is none. All of them exist on IEnumerable<T>; FirstOrNone, LastOrNone, SingleOrNone and ElementAtOrNone also exist on IQueryable<T>, where the predicate is an expression tree and is translated by the query provider.

// Before
var first = numbers.FirstOrDefault();
if (first != 0) // wrong: 0 might be a legitimate first element
{
    ...
}

// After
numbers.FirstOrNone().Switch(
    none: () => ...,
    some: first => ...);

The element type is constrained to notnull. If your sequence contains nulls, filter them out first with WhereNotNull.

See the Option Monad chapter for what to do with the result, and the TryVerb-pattern for the same idea applied to the rest of the BCL.

FirstOrNone

Returns the first element of a sequence, or None if the sequence is empty. With a predicate, returns the first element that satisfies it, or None if no element does.

Option<TSource> FirstOrNone<TSource>(this IEnumerable<TSource> source)
Option<TSource> FirstOrNone<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
A marble diagram showing the FirstOrNone operation

FirstOrNone stops enumerating as soon as it has found an element, so it is safe to call on an infinite sequence as long as a matching element exists.

Example

var numbers = Sequence.Return(3, 1, 4, 1, 5);

numbers.FirstOrNone();              // Some(3)
numbers.FirstOrNone(n => n > 3);    // Some(4)
numbers.FirstOrNone(n => n > 10);   // None
Enumerable.Empty<int>().FirstOrNone(); // None

A typical use is to replace a FirstOrDefault followed by a null check with a chain that keeps the context:

Option<string> ownerName = accounts
    .FirstOrNone(account => account.Id == id)
    .Select(account => account.Owner.Name);

LastOrNone

Returns the last element of a sequence, or None if the sequence is empty. With a predicate, returns the last element that satisfies it, or None if no element does.

Option<TSource> LastOrNone<TSource>(this IEnumerable<TSource> source)
Option<TSource> LastOrNone<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
A marble diagram showing the LastOrNone operation

Unlike FirstOrNone, LastOrNone has to enumerate the whole sequence before it can answer, so it never returns on an infinite sequence.

Example

var numbers = Sequence.Return(3, 1, 4, 1, 5);

numbers.LastOrNone();               // Some(5)
numbers.LastOrNone(n => n < 3);     // Some(1)
numbers.LastOrNone(n => n > 10);    // None

SingleOrNone

Returns the only element of a sequence, or None if the sequence is empty. With a predicate, returns the only element that satisfies it.

Option<TSource> SingleOrNone<TSource>(this IEnumerable<TSource> source)
Option<TSource> SingleOrNone<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
A marble diagram showing the SingleOrNone operation

SingleOrNone keeps the contract of SingleOrDefault: an empty sequence is a legitimate answer and yields None, but more than one matching element is a programming error and throws an InvalidOperationException. If "zero or more than one" should both be None, use Where and a count, or FirstOrNone if you only care about the first match.

To find out whether there is exactly one element, SingleOrNone has to look at the second one, so it enumerates at most two elements.

Example

Sequence.Return(4).SingleOrNone();             // Some(4)
Enumerable.Empty<int>().SingleOrNone();        // None
Sequence.Return(4, 4).SingleOrNone();          // throws InvalidOperationException

var users = Sequence.Return("alice", "bob");
users.SingleOrNone(u => u.StartsWith('a'));    // Some("alice")
users.SingleOrNone(u => u.StartsWith('z'));    // None

ElementAtOrNone

Returns the element at the given position, or None if the position is outside the sequence.

Option<TSource> ElementAtOrNone<TSource>(this IEnumerable<TSource> source, int index)
Option<TSource> ElementAtOrNone<TSource>(this IEnumerable<TSource> source, Index index)   // .NET 6+
A marble diagram showing the ElementAtOrNone operation

A negative index is not an error, it is simply not in the sequence and yields None. The Index overload accepts positions from the end (^1 is the last element); counting from the end may require enumerating the whole sequence.

Example

var numbers = Sequence.Return(3, 1, 4, 1, 5);

numbers.ElementAtOrNone(2);     // Some(4)
numbers.ElementAtOrNone(5);     // None
numbers.ElementAtOrNone(-1);    // None
numbers.ElementAtOrNone(^1);    // Some(5)

MinOrNone

Returns the smallest element of a sequence, or None if the sequence is empty.

Option<TSource> MinOrNone<TSource>(this IEnumerable<TSource> source)
Option<TSource> MinOrNone<TSource>(this IEnumerable<Option<TSource>> source)
Option<TResult> MinOrNone<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
Option<TResult> MinOrNone<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, Option<TResult>> selector)
A marble diagram showing the MinOrNone operation

The BCL's Min returns null for an empty sequence of nullable values but throws for an empty sequence of int. MinOrNone treats both the same: empty is None. Elements are compared with Comparer<T>.Default, and when several elements are equal the first one wins.

The overloads taking Option<T> elements, or a selector returning Option<T>, skip the None elements, in the same way the BCL's Min skips null. A sequence consisting only of None is therefore empty and yields None.

Example

var numbers = Sequence.Return(3, 1, 4, 1, 5);

numbers.MinOrNone();                       // Some(1)
Enumerable.Empty<int>().MinOrNone();       // None
numbers.MinOrNone(n => n * -1);            // Some(-5)

var readings = Sequence.Return(Option.Some(3), Option<int>.None, Option.Some(2));
readings.MinOrNone();                      // Some(2)

MaxOrNone

Returns the largest element of a sequence, or None if the sequence is empty.

Option<TSource> MaxOrNone<TSource>(this IEnumerable<TSource> source)
Option<TSource> MaxOrNone<TSource>(this IEnumerable<Option<TSource>> source)
Option<TResult> MaxOrNone<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
Option<TResult> MaxOrNone<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, Option<TResult>> selector)
A marble diagram showing the MaxOrNone operation

MaxOrNone mirrors MinOrNone in every respect: empty is None, comparison uses Comparer<T>.Default, the first of several equal elements wins, and None elements are skipped.

Example

var numbers = Sequence.Return(3, 1, 4, 1, 5);

numbers.MaxOrNone();                       // Some(5)
Enumerable.Empty<int>().MaxOrNone();       // None
numbers.MaxOrNone(n => n % 3);             // Some(2)

MinByOrNone

Returns the element with the smallest key, or None if the sequence is empty.

Option<TSource> MinByOrNone<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
Option<TSource> MinByOrNone<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
A marble diagram showing the MinByOrNone operation

Where MinOrNone with a selector returns the smallest key, MinByOrNone returns the element that has it. This is the same distinction as between Min(selector) and MinBy in the BCL, which gained MinBy in .NET 6 and throws on an empty sequence of value types. MinByOrNone is available on every target framework and returns None instead.

The first element with the smallest key wins when there are ties. The key selector is called once per comparison, so keep it cheap or project the key first.

Example

var animals = Sequence.Return(
    new Animal("Elephant", Weight: 6000),
    new Animal("Mouse", Weight: 0.02),
    new Animal("Cat", Weight: 4),
    new Animal("Dog", Weight: 30));

animals.MinByOrNone(animal => animal.Weight);     // Some(Mouse)
animals.MinOrNone(animal => animal.Weight);       // Some(0.02)
Enumerable.Empty<Animal>().MinByOrNone(a => a.Weight); // None

MaxByOrNone

Returns the element with the largest key, or None if the sequence is empty.

Option<TSource> MaxByOrNone<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
Option<TSource> MaxByOrNone<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
A marble diagram showing the MaxByOrNone operation

MaxByOrNone mirrors MinByOrNone: it returns the element rather than the key, the first element wins on ties, and an empty sequence yields None instead of throwing like the BCL's MaxBy.

Example

animals.MaxByOrNone(animal => animal.Weight);     // Some(Elephant)
animals.MaxByOrNone(animal => animal.Name.Length, StringComparer.Ordinal); // Some(Elephant)

AverageOrNone

Returns the arithmetic mean of a sequence of numbers, or None if the sequence is empty.

Option<double> AverageOrNone(this IEnumerable<int> source)
Option<double> AverageOrNone(this IEnumerable<long> source)
Option<float> AverageOrNone(this IEnumerable<float> source)
Option<double> AverageOrNone(this IEnumerable<double> source)
Option<decimal> AverageOrNone(this IEnumerable<decimal> source)

Each of these also exists for a sequence of Option<T> of the same numeric type, and with a selector (Func<TSource, int>, Func<TSource, Option<int>>, and so on), matching the overload set of the BCL's Average.

A marble diagram showing the AverageOrNone operation

The BCL's Average throws an InvalidOperationException on an empty sequence of non-nullable numbers, and returns null on an empty sequence of nullable numbers. AverageOrNone returns None in both cases. None elements are skipped and do not count towards the divisor, exactly like null elements in the BCL.

The result type follows the BCL: int and long average to double, the floating point types to themselves.

Example

Sequence.Return(3, 1, 4, 1, 6).AverageOrNone();    // Some(3.0)
Enumerable.Empty<int>().AverageOrNone();           // None

var temperatures = Sequence.Return(Option.Some(20.0), Option<double>.None, Option.Some(22.0));
temperatures.AverageOrNone();                      // Some(21.0)

orders.AverageOrNone(order => order.Total);        // Option<decimal>

GetNonEnumeratedCountOrNone

Returns the number of elements if it can be determined without enumerating the sequence, otherwise None. Available on .NET 6 and later.

Option<int> GetNonEnumeratedCountOrNone<TSource>(this IEnumerable<TSource> source)

This wraps the BCL's TryGetNonEnumeratedCount, which succeeds for collections that know their size, such as arrays, List<T> and ICollection<T>, and for some LINQ operators over them. It fails for anything that would need to be iterated, for example the result of a Where.

Use it when you want to size a buffer or show a count up front but must not pay for a full enumeration, and have a fallback for when the count is unknown:

string Describe<T>(IEnumerable<T> items)
    => items.GetNonEnumeratedCountOrNone()
        .Select(count => $"{count} items")
        .GetOrElse("some items");