Skip to content

Commit 93b8f90

Browse files
marius-bughiuclaude
andcommitted
docs(BTree): stop conflating a null key with default(TKey)
Comparer<T>.Default orders null before every non-null value, which is what makes a null key or element legal here — but that says nothing about default(TKey) for a value type: default(int) is 0, and sorts after every negative key like any other. The B-trees have no out-of-band default slot at all, unlike the hash-based family, so default(TKey) is simply an ordinary key. Reworded in the BTreeDictionary / BTreeSet / DefaultComparer XML remarks and in both places in docs/api/collections.md, and renamed the two tests whose names claimed default(TKey) sorts first (the assertions were already correct: 0 lands between -5 and 5). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 33e8e6d commit 93b8f90

6 files changed

Lines changed: 21 additions & 13 deletions

File tree

docs/api/collections.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3781,7 +3781,7 @@ There is no capacity or load-factor parameter — a B-tree grows one node at a t
37813781
| `Enumerator GetEnumerator()` | Struct enumerator over the entries in ascending key order. The traversal path is held in an inline buffer, so a `foreach` allocates nothing. |
37823782
| `void CopyTo(KeyValuePair<TKey, TValue?>[] array, int arrayIndex)` | Copy every entry in key order. |
37833783

3784-
Unlike `SortedDictionary`, a **`null` (or `default`) key is legal** and sorts before every other key, matching how the rest of the family treats the out-of-band default key; a custom `TComparer` that rejects `null` overrides that. Adding, removing, and clearing invalidate active enumerators (`InvalidOperationException` from `MoveNext`); lookups, a rejected duplicate `TryAdd`, a `Remove` of an absent key, and an in-place value overwrite do not. Not thread-safe.
3784+
Unlike `SortedDictionary`, a **`null` key is legal**: `Comparer<TKey>.Default` orders `null` before every non-`null` key (a custom `TComparer` that rejects `null` overrides that). There is no out-of-band `default(TKey)` slot as in the hash-based familya value-type `default(TKey)` is an ordinary key that sorts wherever the comparer puts it, so `0` follows every negative `int` rather than coming first. Adding, removing, and clearing invalidate active enumerators (`InvalidOperationException` from `MoveNext`); lookups, a rejected duplicate `TryAdd`, a `Remove` of an absent key, and an in-place value overwrite do not. Not thread-safe.
37853785

37863786
### Usage example
37873787

@@ -3859,7 +3859,7 @@ As with the dictionary, there is no capacity or load factor. The `IEnumerable` o
38593859
| `void CopyTo(T[] array, int arrayIndex)` | Copy every element in ascending order. |
38603860
| `Enumerator GetEnumerator()` | Allocation-free struct enumerator in ascending order. |
38613861

3862-
Membership is defined by `TComparer` — two elements are the same element when the comparer orders them equal. The set-algebra members materialize the right-hand side into a `HashSet<T>`, so they compare *that* side with `EqualityComparer<T>.Default` (matching the rest of the family). A custom comparer that treats two values as equal when `EqualityComparer<T>.Default` does nota case-insensitive order, saycan therefore disagree with `SortedSet<T>` on those members alone. A `null` (or `default`) element is legal and sorts first. Not thread-safe.
3862+
Membership is defined by `TComparer` — two elements are the same element when the comparer orders them equal. The set-algebra members materialize the right-hand side into a `HashSet<T>`, so they compare *that* side with `EqualityComparer<T>.Default` (matching the rest of the family). A custom comparer that treats two values as equal when `EqualityComparer<T>.Default` does nota case-insensitive order, saycan therefore disagree with `SortedSet<T>` on those members alone. A `null` element is legal and `Comparer<T>.Default` orders it before every non-`null` one; a value-type `default(T)` is just an ordinary element, sorted wherever the comparer puts it. Not thread-safe.
38633863

38643864
### Usage example
38653865

src/Celerity.Tests/Collections/BTreeDictionaryTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,10 @@ public void AddAndRemove_ShouldStayConsistent_WhenInterleaved()
396396
// ---- the default / null key ---------------------------------------------
397397

398398
[Fact]
399-
public void Add_ShouldAcceptDefaultKey_AndSortItFirst()
399+
public void Add_ShouldTreatDefaultKeyAsOrdinary_WhenKeyIsAValueType()
400400
{
401+
// There is no out-of-band default-key slot here: default(int) is 0, an ordinary key that sorts
402+
// between the negatives and the positives rather than first.
401403
var dict = new BTreeDictionary<int, string>();
402404
dict.Add(5, "five");
403405
dict.Add(0, "zero");
@@ -410,7 +412,7 @@ public void Add_ShouldAcceptDefaultKey_AndSortItFirst()
410412
}
411413

412414
[Fact]
413-
public void Add_ShouldAcceptNullKey_AndSortItFirst()
415+
public void Add_ShouldAcceptNullKey_AndSortItBeforeEveryNonNullKey()
414416
{
415417
// Comparer<string>.Default orders null before every non-null key, so — unlike SortedDictionary —
416418
// a null key is a legal, well-ordered key here.

src/Celerity.Tests/Collections/BTreeSetTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ public void AddAndRemove_ShouldStayConsistent_WhenInterleaved()
246246
}
247247

248248
[Fact]
249-
public void Add_ShouldAcceptNullElement_AndSortItFirst()
249+
public void Add_ShouldAcceptNullElement_AndSortItBeforeEveryNonNullElement()
250250
{
251251
var set = new BTreeSet<string>();
252252
set.Add("b");

src/Celerity/Collections/BTreeDictionary.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,12 @@ public BTreeDictionary(IEnumerable<KeyValuePair<TKey, TValue>> source)
7575
/// <see cref="CelerityDictionary{TKey, TValue, THasher}"/> instead.
7676
/// </para>
7777
/// <para>
78-
/// Unlike <see cref="SortedDictionary{TKey, TValue}"/>, a <c>null</c> (or <c>default</c>) key is legal and
79-
/// sorts before every other key, matching how the rest of the family treats the out-of-band default key;
80-
/// a custom <typeparamref name="TComparer"/> that rejects <c>null</c> overrides that. This type is not
81-
/// thread-safe; concurrent callers must synchronize externally.
78+
/// Unlike <see cref="SortedDictionary{TKey, TValue}"/>, a <c>null</c> key is legal:
79+
/// <see cref="Comparer{T}.Default"/> orders <c>null</c> before every non-<c>null</c> key (a custom
80+
/// <typeparamref name="TComparer"/> that rejects <c>null</c> overrides that). There is no out-of-band
81+
/// <c>default(TKey)</c> slot as in the hash-based family — a value-type <c>default(TKey)</c> is an ordinary
82+
/// key that sorts wherever the comparer puts it, so <c>0</c> follows every negative <see cref="int"/> rather
83+
/// than coming first. This type is not thread-safe; concurrent callers must synchronize externally.
8284
/// </para>
8385
/// </remarks>
8486
public class BTreeDictionary<TKey, TValue, TComparer>

src/Celerity/Collections/BTreeSet.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ public BTreeSet(IEnumerable<T> source)
6767
/// <see cref="EqualityComparer{T}.Default"/>, matching the rest of the family. A custom comparer that treats
6868
/// two values as equal when <see cref="EqualityComparer{T}.Default"/> does not — a case-insensitive order,
6969
/// say — can therefore disagree with <see cref="SortedSet{T}"/> on those members alone. Like the rest of the family, <see cref="Add"/> throws
70-
/// on a duplicate — use <see cref="TryAdd"/> when the element may already be present. A <c>null</c> (or
71-
/// <c>default</c>) element is legal and sorts first. This type is not thread-safe; concurrent callers must
72-
/// synchronize externally.
70+
/// on a duplicate — use <see cref="TryAdd"/> when the element may already be present. A <c>null</c> element is
71+
/// legal and <see cref="Comparer{T}.Default"/> orders it before every non-<c>null</c> one; there is no
72+
/// out-of-band <c>default(T)</c> slot as in the hash-based family, so a value-type <c>default(T)</c> is an
73+
/// ordinary element that sorts wherever the comparer puts it (<c>0</c> follows every negative
74+
/// <see cref="int"/>). This type is not thread-safe; concurrent callers must synchronize externally.
7375
/// </para>
7476
/// </remarks>
7577
public class BTreeSet<T, TComparer> : ISet<T>, IReadOnlySet<T>

src/Celerity/Collections/DefaultComparer.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ namespace Celerity.Collections;
2929
/// </code>
3030
/// <para>
3131
/// A <c>null</c> argument is not rejected: <see cref="Comparer{T}.Default"/> orders <c>null</c> before every
32-
/// non-<c>null</c> value, which is what makes <c>default(TKey)</c> a legal key in the ordered collections.
32+
/// non-<c>null</c> value, which is what makes a <c>null</c> key or element legal in the ordered collections.
33+
/// That says nothing about <c>default(T)</c> for a value type — <c>default(int)</c> is simply <c>0</c>, and
34+
/// sorts after every negative value like any other key.
3335
/// </para>
3436
/// </remarks>
3537
public readonly struct DefaultComparer<T> : IComparer<T>

0 commit comments

Comments
 (0)