Overview
Masterly.NonEmptyList gives .NET collections that the type system guarantees are never empty. NonEmptyList<T> is a list with at least one element, so Head, Last, Max, Average and Reduce cannot fail for lack of data. Rules such as "an order has at least one line" or "a route has at least one waypoint" appear in your method signatures instead of in comments and defensive checks.
Many LINQ operators on IEnumerable<T>, such as First, Max, Average and Aggregate, throw InvalidOperationException: Sequence contains no elements when the sequence is empty. On a NonEmptyList<T> that failure cannot happen. The list keeps the guarantee through every interface it implements. Removing the last element through IList<T>, ICollection<T> or the non-generic IList throws instead of emptying the list.
The core package targets .NET 8, .NET 9 and .NET 10 and has no dependencies. It includes a Roslyn analyzer, code fixes and a source generator. Seven optional packages add support for ASP.NET Core, configuration, EF Core, Newtonsoft.Json, MessagePack, Dapper and testing.
How it works
Working with a non-empty list has four stages. Select a stage to read its guide.
- CreateBuild a list that has at least one element, or find out up front that you cannot.
[1, 2, 3]NonEmptyList.Of(. . . ) NonEmptyList<T>.TryFrom - ReadHead and Last never throw. List patterns, ranges and deconstruction work as usual.
Head Last[var head, .. var rest] xs[1.. ] - TransformLINQ keeps the type whenever the result cannot be empty, and says so when it can.
Select OrderBy GroupByMap FlatMap ScanWhere → IEnumerable<T> - AggregateNo seed, no nullable result and no empty-sequence exception.
Max() Min() Sum()Average() Median()Reduce(f)
What you can do
- Create listsCollection expressions, factories and safe conversions from any sequence.
- Change lists safelyThe full List<T> surface, with every path that would empty the list blocked.
- Query with LINQOperators that cannot produce an empty result keep returning a NonEmptyList.
- AggregateSum, Average, Median and Mode for any System.Numerics type, with no empty case.
- More non-empty typesSets, dictionaries, strings, immutable lists, spans and lazy sequences.
- Model your domainA zipper, an undo history, event streams and rose trees that always have a value.
- Constrain the shapeAt least two, at most ten, distinct or sorted, all checked when the list is created.
- Validate inputCombine checks and report every error at once, never an invalid result with none.
- Batch streamsTurn async streams and channels into non-empty batches by size or by time.
- Catch mistakes earlyThe bundled analyzer flags empty input and throwing LINQ calls at build time.
- Use it in ASP.NET CoreBind from query strings, publish OpenAPI schemas, return problem details.
- Store it with EF CoreJSON columns, non-empty relationships and LINQ that still translates.
Where to go next
- New hereQuick StartCreate a list, read it safely, query it and handle empty input.
- See it in contextReal-world examplesOrders, API endpoints, batch consumers, configuration and wizards.
- Coming from 2.xUpgrading from 2.xWhat changed in 3.0 and how to migrate each breaking change.
- Looking something upAPI referenceEvery public type and member, one package at a time.