PotternMotching 0.4.2
dotnet add package PotternMotching --version 0.4.2
NuGet\Install-Package PotternMotching -Version 0.4.2
<PackageReference Include="PotternMotching" Version="0.4.2" />
<PackageVersion Include="PotternMotching" Version="0.4.2" />
<PackageReference Include="PotternMotching" />
paket add PotternMotching --version 0.4.2
#r "nuget: PotternMotching, 0.4.2"
#:package PotternMotching@0.4.2
#addin nuget:?package=PotternMotching&version=0.4.2
#tool nuget:?package=PotternMotching&version=0.4.2
PotternMotching
A fluent pattern matching library for .NET that provides powerful patterns for values, collections, and dictionaries with automatic pattern generation from records and external types.
Installation
dotnet add package PotternMotching
Quick Start
Value Matching
using PotternMotching.Patterns;
var pattern = ValuePattern.Exact(42);
pattern.Evaluate(42); // Success
pattern.Evaluate(43); // Failure: Expected 42, got 43
Collection Matching
// Match all items in any order (subset matching)
var pattern = CollectionPattern.Subset(["apple", "banana"]);
pattern.Evaluate(["banana", "cherry", "apple"]); // Success - all patterns found
// Match exact sequence
var sequence = CollectionPattern.Sequence(["a", "b", "c"]);
sequence.Evaluate(["a", "b", "c"]); // Success
sequence.Evaluate(["a", "b"]); // Failure: wrong length
// Match prefix
var prefix = CollectionPattern.StartsWith(["hello", "world"]);
prefix.Evaluate(["hello", "world", "!"]); // Success
// Match suffix
var suffix = CollectionPattern.EndsWith(["!", "!"]);
suffix.Evaluate(["wow", "!", "!"]); // Success
// Match any element
var anyMatch = CollectionPattern.AnyElement("target");
anyMatch.Evaluate(["foo", "target", "bar"]); // Success
Dictionary Matching
using PotternMotching.Patterns;
// Match specified keys (allows extra keys)
var pattern = DictionaryPattern.Items(new Dictionary<string, IPattern<int>>
{
["timeout"] = ValuePattern.Exact(30)
});
pattern.Evaluate(new Dictionary<string, int>
{
["timeout"] = 30,
["retries"] = 3 // Extra keys OK
}); // Success
// Match exact keys (no extra keys allowed)
var exactPattern = DictionaryPattern.ExactItems(new Dictionary<string, IPattern<int>>
{
["timeout"] = ValuePattern.Exact(30)
});
exactPattern.Evaluate(new Dictionary<string, int>
{
["timeout"] = 30,
["retries"] = 3 // Extra key causes failure
}); // Failure: Unexpected keys: 'retries'
Automatic Pattern Generation
Declare target types with [AutoPatternFor(typeof(...))] on a marker class to automatically generate pattern classes with smart defaults:
using PotternMotching;
public record Person(string Name, int Age);
public record Address(string City, string Zip);
public record Company(
string Name,
Address HeadOffice,
Address[] Branches,
HashSet<string> Tags);
[AutoPatternFor(typeof(Person))]
[AutoPatternFor(typeof(Address))]
[AutoPatternFor(typeof(Company))]
internal static class PatternMarkers;
The source generator creates pattern classes you can use immediately:
// Create patterns - all properties are optional (default = match anything)
var pattern = new CompanyPattern(
Name: "Acme Corp", // Match exact name
HeadOffice: new AddressPattern(City: "Seattle"), // Partial match - any zip
Branches: [ // Sequence match (exact order & length)
new AddressPattern(Zip: "98101"),
new AddressPattern(Zip: "98102")
],
Tags: ["technology", "software"] // Subset match (unordered)
);
var company = new Company(
"Acme Corp",
new Address("Seattle", "98101"),
[
new Address("Portland", "98101"),
new Address("San Francisco", "98102")
],
["technology", "software", "cloud"] // Extra tag is OK for HashSet
);
var result = pattern.Evaluate(company); // Success
External Types
The same attribute works for types you do not own.
// In another assembly
namespace Shared.Contracts;
public class ExternalUserDto
{
public string Id { get; init; } = string.Empty;
public string Name { get; init; } = string.Empty;
public string[] Roles { get; init; } = [];
}
// In your test project or consumer project
using PotternMotching;
using Shared.Contracts;
namespace MyProject.Tests.Patterns;
[AutoPatternFor(typeof(ExternalUserDto))]
internal static class ExternalPatterns;
This generates a top-level pattern type in the marker namespace:
using MyProject.Tests.Patterns;
var pattern = new ExternalUserDtoPattern(
Id: "42",
Roles: ["admin"]
);
Notes:
[AutoPatternFor]supports records, classes, and Dunet unions- for classes, all public instance properties with a public getter may be matched
- Dunet union roots generate variant-aware patterns
- the generated type name is always
{TypeName}Pattern - the generated type is emitted into the marker type namespace
- nested types are matched as nested patterns only when a pattern is already known; otherwise they fall back to exact value matching
Flexible Matching with Defaults
Properties you don't specify match anything:
// Only check the name and city
var flexiblePattern = new CompanyPattern(
Name: "Acme Corp",
HeadOffice: new AddressPattern(City: "Seattle")
// Branches and Tags not specified - will match any value
);
Collection Expressions & Implicit Conversions
Generated patterns support C# collection expressions and implicit conversions:
// Mix patterns and values in collection literals
SequencePatternDefault<Address, AddressPattern> branches = [
new AddressPattern(City: "Portland"), // Pattern
new Address("Seattle", "98101"), // Value - implicitly converted!
];
// Convert entire objects to patterns
CompanyPattern pattern = company; // Implicit conversion
Supported Types for Auto-Pattern Generation
The source generator automatically maps types to appropriate pattern wrappers:
| Your Type | Generated Pattern Property Type | Matching Behavior |
|---|---|---|
int, string, primitives |
PatternDefault<T, ValuePattern<T>.Exact> |
Exact equality |
T[], List<T>, IEnumerable<T> |
SequencePatternDefault<T, ...> |
Exact sequence (order + length) |
HashSet<T>, ISet<T> |
SetPatternDefault<T, ...> |
Subset (unordered, allows extras) |
Dictionary<K,V>, IDictionary<K,V> |
DictionaryPatternDefault<K,V, ...> |
Key-value pairs (allows extra keys) |
Nested pattern-capable types targeted with [AutoPatternFor] |
RecordNamePattern? |
Nested pattern matching |
| Discriminated unions (Dunet) | Variant-specific patterns | Variant-aware matching |
Pattern Types Reference
Value Patterns
using PotternMotching.Patterns;
// Exact equality matching
ValuePattern.Exact(value)
Collection Patterns
using PotternMotching.Patterns;
// All patterns must be found in any order (allows extras)
CollectionPattern.Subset(items)
// Exact sequence - same order and length
CollectionPattern.Sequence(items)
// Collection must start with these items
CollectionPattern.StartsWith(items)
// Collection must end with these items
CollectionPattern.EndsWith(items)
// At least one element must match
CollectionPattern.AnyElement(pattern)
All collection pattern factory methods accept either arrays of values or arrays of patterns.
Dictionary Patterns
using PotternMotching.Patterns;
// All specified key-value pairs must match (allows extra keys)
DictionaryPattern.Items(pairs)
// Exact keys - no more, no less (no extra keys allowed)
DictionaryPattern.ExactItems(pairs)
Match Results & Error Messages
All patterns return a MatchResult - a discriminated union with detailed error information:
var result = pattern.Evaluate(value);
result.Match(
success => Console.WriteLine("Matched!"),
failure => Console.WriteLine($"Failed:\n{failure}")
);
MatchResult has two cases:
MatchResult.Success- Pattern matched successfullyMatchResult.Failure(string[] Reasons)- Pattern didn't match, with detailed path-based reasons
Detailed Error Messages
PotternMotching provides precise error messages with full paths:
var pattern = new CompanyPattern(
Branches: [
new AddressPattern(Zip: "98101"),
new AddressPattern(Zip: "98102")
]
);
var company = new Company(
"Acme",
null!,
[
new Address("Portland", "99999"), // Wrong zip!
new Address("Seattle", "98102")
],
[]
);
var result = pattern.Evaluate(company);
// Failure:
// - .Branches[0].Zip: [ValuePattern.Exact] Expected 98101, got 99999
Test Assertions
Use the AssertPattern extension method for concise pattern assertions with automatic expression capture:
using PotternMotching;
var person = new Person("Alice", 30);
var pattern = new PersonPattern(Name: "Alice", Age: 30);
person.AssertPattern(pattern); // Throws AssertionFailedException if no match
// On failure, automatically includes the variable name in the error:
// FAILURE: person.Age: [ValuePattern.Exact] Expected 25, got 30
For exact value comparison without explicitly constructing a pattern, use AssertExact:
person.AssertExact(new Person("Alice", 30));
For subset assertions on collections, use AssertSubset:
var tags = new[] { "important", "backend", "urgent" };
tags.AssertSubset(["important", "urgent"]);
For exact sequence assertions on collections, use AssertSequence:
var values = new[] { 1, 2, 3 };
values.AssertSequence([1, 2, 3]);
For prefix and suffix assertions on collections, use AssertStartsWith and AssertEndsWith:
values.AssertStartsWith([1, 2]);
values.AssertEndsWith([2, 3]);
Advanced: Discriminated Unions
PotternMotching integrates with Dunet for discriminated union support:
using Dunet;
using PotternMotching;
[Union]
public partial record Job
{
public partial record Employed(string Company, string Position);
public partial record Unemployed;
}
public record Person(string Name, Job Job);
[AutoPatternFor(typeof(Job))]
[AutoPatternFor(typeof(Person))]
internal static class PatternMarkers;
Generated patterns are variant-aware:
var pattern = new PersonPattern(
Name: "Alice",
Job: new JobPattern.Employed(
Company: "Tech Corp",
Position: "Developer"
)
);
var person = new Person("Alice", new Job.Employed("Tech Corp", "Developer"));
var result = pattern.Evaluate(person); // Success
var unemployed = new Person("Bob", new Job.Unemployed());
var result2 = pattern.Evaluate(unemployed);
// Failure: .Job: Expected variant Employed, got Unemployed
Advanced: Custom Patterns
Override default matching behavior by providing explicit patterns:
var pattern = new CompanyPattern(
Name: "Acme Corp",
// Use CollectionPattern.StartsWith instead of default Sequence
Branches: CollectionPattern.StartsWith([
new AddressPattern(City: "Seattle")
]),
// Use custom pattern for Tags
Tags: CollectionPattern.AnyElement("important")
);
Requirements
- .NET 10.0 or later
- C# 12 or later (for collection expressions and source generator features)
Examples
See the samples directory for complete working examples.
License
MIT License - see LICENSE file for details
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Dunet (>= 1.15.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on PotternMotching:
| Package | Downloads |
|---|---|
|
PotternMotching.Sample
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.