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)
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)
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)
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 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)
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)
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)
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)
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.
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");