One guarantee, in every kind of C# you write
Collection expressions, list patterns, query syntax and generic math all know the list is non-empty, so the code you already write stops needing defensive checks.
- Collection expressions
- LINQ
- Generic math
- Constraints
- Validation
Create lists with [..], match them with list patterns and slice them with ranges. Head never throws. Learn more
NonEmptyList<int> numbers = [3, 1, 2];
var names = NonEmptyList.Of("Ada", "Grace", "Linus");
int first = numbers.Head;
int last = numbers.Last;
if (numbers is [var head, .. var rest])
{
Console.WriteLine($"{head} then {rest.Count} more");
}
List<int> tail = numbers[1..];
Operators that cannot produce an empty result return a NonEmptyList. The rest fall back to IEnumerable. Learn more
NonEmptyList<string> sorted =
from n in names
orderby n.Length, n
select n.ToUpperInvariant();
NonEmptyList<int> doubled = numbers.Select(n => n * 2); // not IEnumerable<int>
IEnumerable<int> big = numbers.Where(n => n > 10); // may be empty, so not promoted
Aggregations with no empty-sequence path, for every type that implements the System.Numerics interfaces. Learn more
int max = numbers.Max(); // int, not int?
double average = numbers.Average();
int sum = numbers.Sum(); // no seed needed
double median = numbers.Median();
Money total = invoice.Lines.Select(l => l.Amount).Sum(); // any type with a + operator
Put cardinality, uniqueness and ordering in the type. Members such as Second() exist only when proven. Learn more
ConstrainedList<Email, And<AtLeast<Two>, Distinct>> recipients = [a, b, c];
ConstrainedList<Candidate, AtLeast<Two>> finalists = ranked.Constrain<Candidate, AtLeast<Two>>();
Candidate winner = finalists.Head;
Candidate runnerUp = finalists.Second(); // compiles only for AtLeast<Two> and up
Collect every error instead of stopping at the first. An invalid result always has at least one error. Learn more
Validation<User, ValidationError> user = Validation.Combine(
Validation.NotBlank(input.Name, new ValidationError("required", "Name is required", "name")),
Validation.NotBlank(input.Email, new ValidationError("required", "Email is required", "email")),
Validation.Ensure(input.Age, a => a >= 18, new ValidationError("range", "Must be an adult", "age")),
(name, email, age) => new User(name, email, age));
return user.ToResult(); // 200 with the user, or 400 validation problem details
Everything a non-empty collection needs in production
Start by replacing one List<T>. Add constraints, validation and framework integration when you need them.
Type-preserving LINQ
Select, OrderBy, GroupBy, Distinct, Concat and Chunk keep the guarantee. Where, Skip and Take return IEnumerable, so the type never lies.
Read the guide| On a NonEmptyList<T> | Returns |
|---|---|
| Select(f) | NonEmptyList<R> |
| OrderBy(k) | OrderedNonEmptyList<T> |
| GroupBy(k) | NonEmptyList<NonEmptyGrouping<K, T>> |
| Max() | T |
| Where(p) | IEnumerable<T>may be empty |
A guarantee you cannot break
The list wraps its storage. Emptying it through IList<T>, ICollection<T> or IList throws instead of succeeding.
Read the guideGeneric math
Sum, Product, Average, Median and Mode for any System.Numerics type, including your own.
Read the guideErrors that accumulate
Validation<T, TError> combines up to eight checks and reports every failure at once.
Read the guideSets, dictionaries, strings
NonEmptySet, NonEmptyDictionary, NonEmptyString, immutable lists, spans and lazy sequences.
Read the guideConstraints in the type
AtLeast, AtMost, Exactly, Distinct and Sorted, composed with And and Or, checked once at construction.
Read the guide| ConstrainedList<T, …> | You get |
|---|---|
| NonEmpty | Head, Last, Max() |
| AtLeast<Two> | + Second() |
| AtLeast<Three> | + Third() |
| And<AtLeast<Two>, Distinct> | Second(), no duplicates |
Modeling types
A zipper that always has a focus, undo history, event streams with an origin, and rose trees.
Read the guideNon-empty batching
Batch streams and channels into NonEmptyList batches, by size or by time. No more empty-batch checks.
Read the guideAnalyzer and generator
Catch [] and provably empty input at build time, and generate domain collections from one attribute.
Read the guideWorks with your stack
Query-string binding, OpenAPI schemas, options validation, EF Core, Json.NET, MessagePack and Dapper.
Read the guideInstall the core. Add only what you use.
All eight packages ship together with the same version number. Every companion package depends only on the core and the library it integrates with.
Masterly.NonEmptyListThe collection types, LINQ, generic math, Validation and constraints, plus the bundled analyzer and source generator.
Masterly.NonEmptyList.AspNetCoreOpenAPI minItems and minLength for every non-empty type (.NET 9+), and Validation to problem details.
Masterly.NonEmptyList.ConfigurationOptions binding that refuses to start the host when a required collection is empty.
Masterly.NonEmptyList.EntityFrameworkCoreJSON column converters, non-empty relationships, SaveChanges validation and query translation.
Masterly.NonEmptyList.NewtonsoftJsonOne converter for every non-empty type. Empty arrays are rejected on read.
Masterly.NonEmptyList.MessagePackFormatters and a resolver for every non-empty type. Empty payloads are rejected.
Masterly.NonEmptyList.DapperType handlers that store non-empty collections as JSON text columns.
Masterly.NonEmptyList.TestingCsCheck generators, an AutoFixture customization and the published law suite.
Replace your first List<T> in five minutes.
The Quick Start creates a list, reads it safely, queries it and handles empty input.