Copse.Async
0.3.0-alpha.20
dotnet add package Copse.Async --version 0.3.0-alpha.20
NuGet\Install-Package Copse.Async -Version 0.3.0-alpha.20
<PackageReference Include="Copse.Async" Version="0.3.0-alpha.20" />
<PackageVersion Include="Copse.Async" Version="0.3.0-alpha.20" />
<PackageReference Include="Copse.Async" />
paket add Copse.Async --version 0.3.0-alpha.20
#r "nuget: Copse.Async, 0.3.0-alpha.20"
#:package Copse.Async@0.3.0-alpha.20
#addin nuget:?package=Copse.Async&version=0.3.0-alpha.20&prerelease
#tool nuget:?package=Copse.Async&version=0.3.0-alpha.20&prerelease
Copse
LINQ for trees. ITreenumerable<T> is to trees what IEnumerable<T> is to sequences — a lazy,
composable abstraction supporting depth-first and breadth-first traversal with 45+ operations:
Where, Select, GetLeaves, PruneSubtreesWhere, SelectMany, LeaffixAggregate, Union,
and more.
No equality contract required: node types need not implement IEquatable<T> or override
GetHashCode.
Install
dotnet add package Copse.Linq --prerelease
Copse.Linq transitively brings in the rest of the sync family (Copse, Copse.Core,
Copse.Primitives). Targets net48, netstandard2.0, netstandard2.1, and
net8.0.
Examples
Adapt any tree shape by implementing IChildEnumerator<THandle> — a struct Copse pulls to
enumerate each node's children. The handle is whatever identifies a node in your structure
(an object reference, an index, here the number itself):
using Copse;
using Copse.Core;
using Copse.Linq;
using Copse.Treenumerables;
using System.Linq;
// Node n has children 2n and 2n+1 — a complete binary tree capped at 7.
struct BinaryChildren : IChildEnumerator<int>
{
private int _next;
private readonly int _last;
private bool _disposed;
public BinaryChildren(int parent)
{
_next = parent * 2; // first child of n is 2n...
_last = parent * 2 + 1; // ...and its second (last) child is 2n+1
_disposed = false;
}
public Option<HandleAndSiblingIndex<int>> MoveNext()
{
if (_disposed || _next > _last || _next > 7)
return default;
var child = new HandleAndSiblingIndex<int>(_next, _next % 2);
_next++;
return new Option<HandleAndSiblingIndex<int>>(child);
}
public void Dispose() => _disposed = true;
}
ITreenumerable<int> tree = Tree.Create(
ctx => new BinaryChildren(ctx.Node), new[] { 1 });
// 1
// / \
// 2 3
// / \ / \
// 4 5 6 7
(This is the node-is-its-own-handle form; Tree.Create also has a three-parameter overload
taking a handle → node map for trees whose surfaced values can't produce their own
children, and Tree.FromTopology/Tree.FromPreorderStore-style doors cover the other
families.)
Once you have an ITreenumerable<T>, the full operation set is available. Operations compose
without materialization when possible — the streaming operators stay lazy end-to-end — and when
an operation does capture the tree (or might), its return type and docs say so:
int[] preOrder = tree.GetPreorderTraversal().ToArray(); // [1, 2, 4, 5, 3, 6, 7]
int[] leaves = tree.GetLeaves().ToArray(); // [4, 5, 6, 7]
// Select transforms values while preserving tree structure
int[] doubled = tree
.Select(node => node * 2)
.GetPreorderTraversal()
.ToArray(); // [2, 4, 8, 10, 6, 12, 14]
// PruneSubtreesWhere removes each matching node with its whole subtree
int[] topTwo = tree
.PruneSubtreesWhere((node, position) => position.Depth >= 2)
.GetLeaves()
.ToArray(); // [2, 3]
Where is structural. A filtered-out node's children are promoted to the nearest remaining
ancestor — unlike IEnumerable.Where, which is a flat element filter:
// Remove even nodes. Children of 2 (which are 4 and 5) become children of 1.
// 4 and 6 are also removed but have no children, so they simply vanish.
int[] filtered = tree
.Where(node => node % 2 != 0)
.GetPreorderTraversal()
.ToArray();
// Result tree: 1(5, 3(7)) => [1, 5, 3, 7]
LeaffixAggregate folds bottom-up, one value per root: each family's completed child
accumulations are reduced pairwise (the edge accumulator), then the node folds itself in
once (the node accumulator); leaves answer through the leaf selector directly:
int subtreeSum = tree
.LeaffixAggregate(
leaf => leaf, // each leaf's own accumulation
(accumulate, childAccumulate) => accumulate + childAccumulate,
(accumulate, node) => accumulate + node)
.First()
.Accumulate; // each result is a NodeAccumulation: the root's value paired with its fold
// 28 (1 + 2 + 3 + 4 + 5 + 6 + 7)
Walk instead of traversing. Materialize() captures any tree into a walkable buffer whose
TreeWalker navigates freely — parent, child by index, root by index — with GetNode()
reading the focused node:
var capture = tree.Materialize();
var walker = capture.GetTreeWalker().MoveToRoot(0).Value;
int root = walker.GetNode(); // 1
int secondChild = walker.MoveToChild(1).Value.GetNode(); // 3
Serialize and back. Copse.SimpleSerializer speaks a header-free text format in both
layouts — preorder "a(b(d,e),c)" and level-order "a;b,c;d,e" — with deferred parsing
(each enumeration parses exactly as far as it reads):
using Copse.SimpleSerializer;
var parsed = TreeSerializer.DeserializeDepthFirstTree("a(b(d,e),c)");
string roundTrip = parsed.SerializeDepthFirstTree(); // "a(b(d,e),c)"
Packages
| Package | Description |
|---|---|
Copse.Linq |
LINQ-style tree operations (Where, Select, GetLeaves, PruneSubtreesWhere, LeaffixAggregate, Union, tree-walker navigation, …) — the package to install |
Copse |
The traversal engines: the depth-first/breadth-first engine over the child-pull protocol, plus the flat preorder/level-order decoders |
Copse.Core |
The contracts: ITreenumerable<T>, ITreenumerator<T>, the walker tier (TreeWalker, ITreeTopology, IWalkableTreenumerable) |
Copse.Primitives |
The chunked ref-access collections (RefSemiDeque, RefAppendOnlyList) and the disposables — installed transitively, not directly |
Copse.SimpleSerializer |
Header-free text serialization, both layouts, sync and async |
Copse.Core.Async / Copse.Async / Copse.Linq.Async |
The async family — the same surface over awaited pulls, built atop the sync packages (async depends on sync, exactly as async LINQ sits atop the BCL; these are also the codegen sources the sync operators are generated from) |
Documentation
Every public member ships XML documentation (IntelliSense). The examples above and the source are the reference; a documentation site at copselib.org is in progress.
Benchmarks
Performance results are published at copselib.github.io/copse-dotnet.
License
MIT — see LICENSE. © 2023–2026 Jason Boyd.
The disposable utilities in Copse.Disposables (shipped in the Copse.Primitives package:
CompositeDisposable, RefCountDisposable, Disposable.Create, …) are adapted from
System.Reactive (MIT, © .NET Foundation and
Contributors) — same names, same semantics, no new concepts. See
THIRD-PARTY-NOTICES.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 is compatible. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.8
- Copse (>= 0.3.0-alpha.20)
- Copse.Core.Async (>= 0.3.0-alpha.20)
- Copse.Primitives (>= 0.3.0-alpha.20)
- Microsoft.Bcl.AsyncInterfaces (>= 8.0.0)
-
.NETStandard 2.0
- Copse (>= 0.3.0-alpha.20)
- Copse.Core.Async (>= 0.3.0-alpha.20)
- Copse.Primitives (>= 0.3.0-alpha.20)
- Microsoft.Bcl.AsyncInterfaces (>= 8.0.0)
-
.NETStandard 2.1
- Copse (>= 0.3.0-alpha.20)
- Copse.Core.Async (>= 0.3.0-alpha.20)
- Copse.Primitives (>= 0.3.0-alpha.20)
-
net8.0
- Copse (>= 0.3.0-alpha.20)
- Copse.Core.Async (>= 0.3.0-alpha.20)
- Copse.Primitives (>= 0.3.0-alpha.20)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Copse.Async:
| Package | Downloads |
|---|---|
|
Copse.SimpleSerializer
Text-based tree serialization for Copse. |
|
|
Copse.Linq.Async
Async LINQ-style tree operators for Copse: the full Copse.Linq surface (Where, Select, set operations, aggregation, Memoize/Materialize, tree-walker navigation, and more) over awaited pulls. The codegen source of truth the synchronous Copse.Linq package is generated from. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.0-alpha.20 | 0 | 8/27/2026 |
| 0.3.0-alpha.19 | 59 | 8/24/2026 |
| 0.3.0-alpha.18 | 65 | 8/17/2026 |
| 0.3.0-alpha.17 | 67 | 8/5/2026 |
| 0.3.0-alpha.16 | 60 | 8/5/2026 |
| 0.3.0-alpha.15 | 63 | 8/5/2026 |
| 0.3.0-alpha.14 | 71 | 8/5/2026 |
| 0.3.0-alpha.13 | 69 | 8/4/2026 |
| 0.3.0-alpha.12 | 63 | 8/4/2026 |
| 0.3.0-alpha.11 | 71 | 8/4/2026 |
| 0.3.0-alpha.10 | 62 | 8/4/2026 |
| 0.3.0-alpha.9 | 73 | 8/2/2026 |
| 0.3.0-alpha.8 | 66 | 8/2/2026 |
| 0.3.0-alpha.7 | 68 | 8/2/2026 |
| 0.3.0-alpha.6 | 77 | 7/17/2026 |
| 0.3.0-alpha.5 | 74 | 7/10/2026 |
| 0.3.0-alpha.4 | 69 | 7/10/2026 |
| 0.3.0-alpha.3 | 70 | 7/10/2026 |
| 0.3.0-alpha.2 | 71 | 7/9/2026 |