From 754a9a7404d180195f9143a35382aa5175ad229e Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:06:16 -0400 Subject: [PATCH 1/2] D2/D-readme: rewrite the stub README into a real one (closes #118, #102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README was a 7-line stub: title, one-line description, two method signatures with single-sentence summaries. No badges, no installation section, no usage examples, no link to docs site or CHANGELOG. This rewrite, modelled on the DateTime-Extensions canonical README: - Adds the canonical badge collection (NuGet / downloads / PR build / release / License / .NET / GitHub). - Adds Installation, License, and Documentation sections with the standard link set. - Documents both public methods (`IsBetween` exclusive bounds, `IsInRange` inclusive bounds) in a comparison table, then with concrete usage examples covering int, DateTime, string, and a custom IComparable type. - Calls out the null-safety contract explicitly. - Lists the actual target frameworks shipped by the csproj (net462 / netstandard2.0 / net8.0 / net10.0). The opening tagline emphasises *why* a consumer would reach for the library — "...the way you'd say them" framing — instead of just restating the namespace. Public surface unchanged; this is doc-only. --- README.md | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index deaa756..6fd5965 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,114 @@ # Wolfgang.Extensions.IComparable -A collection of extension methods for the `IComparable` interface in C#. +Extension methods for `IComparable` that make ordering and range checks read the way you'd say them — `value.IsBetween(low, high)` instead of `value.CompareTo(low) > 0 && value.CompareTo(high) < 0`. -## Methods +[![NuGet](https://img.shields.io/nuget/v/Wolfgang.Extensions.IComparable.svg?logo=nuget&label=NuGet)](https://www.nuget.org/packages/Wolfgang.Extensions.IComparable) +[![NuGet downloads](https://img.shields.io/nuget/dt/Wolfgang.Extensions.IComparable.svg?logo=nuget&label=downloads)](https://www.nuget.org/packages/Wolfgang.Extensions.IComparable) +[![PR build](https://img.shields.io/github/actions/workflow/status/Chris-Wolfgang/IComparable-Extensions/pr.yaml?event=pull_request_target&label=PR%20build&logo=github)](https://github.com/Chris-Wolfgang/IComparable-Extensions/actions/workflows/pr.yaml) +[![Release](https://img.shields.io/github/actions/workflow/status/Chris-Wolfgang/IComparable-Extensions/release.yaml?label=release&logo=github)](https://github.com/Chris-Wolfgang/IComparable-Extensions/actions/workflows/release.yaml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![.NET](https://img.shields.io/badge/.NET-Multi--Targeted-purple.svg)](https://dotnet.microsoft.com/) +[![GitHub](https://img.shields.io/badge/GitHub-Repository-181717?logo=github)](https://github.com/Chris-Wolfgang/IComparable-Extensions) -- `IsBetween(this T value, T lowerBound, T upperBound)` - Returns true when `value` is strictly greater than `lowerBound` and strictly less than `upperBound`. -- `IsInRange(this T value, T lowerBound, T upperBound)` - Returns true when `value` is greater than or equal to `lowerBound` and less than or equal to `upperBound`. +--- +## 📦 Installation +```bash +dotnet add package Wolfgang.Extensions.IComparable +``` + +**NuGet Package:** [Wolfgang.Extensions.IComparable](https://www.nuget.org/packages/Wolfgang.Extensions.IComparable) + +--- + +## 📄 License + +This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details. + +--- + +## 📚 Documentation + +- **GitHub Repository:** [https://github.com/Chris-Wolfgang/IComparable-Extensions](https://github.com/Chris-Wolfgang/IComparable-Extensions) +- **API Documentation:** https://Chris-Wolfgang.github.io/IComparable-Extensions/ +- **CHANGELOG:** [CHANGELOG.md](CHANGELOG.md) +- **Contributing Guide:** [CONTRIBUTING.md](CONTRIBUTING.md) +- **Development Setup:** [docs/setup.md](docs/setup.md) + +--- + +## ✨ Methods + +All methods are generic extension methods on `T where T : IComparable`. Both throw `ArgumentNullException` when `value` is `null`. + +| Method | Bound semantics | Returns `true` when | +|---|---|---| +| `value.IsBetween(lowerBound, upperBound)` | **Exclusive** on both ends | `lowerBound < value < upperBound` | +| `value.IsInRange(lowerBound, upperBound)` | **Inclusive** on both ends | `lowerBound ≤ value ≤ upperBound` | + +The distinction matters when `value` exactly equals one of the bounds: `IsBetween` returns `false`, `IsInRange` returns `true`. Pick the one whose contract you want. + +--- + +## 🚀 Usage + +### `IsBetween` — strict bounds + +```csharp +using Wolfgang.Extensions.IComparable; + +int score = 75; +bool inOpenRange = score.IsBetween(70, 80); // true — 70 < 75 < 80 +bool atLower = 70.IsBetween(70, 80); // false — 70 is NOT > 70 +bool atUpper = 80.IsBetween(70, 80); // false — 80 is NOT < 80 +``` + +### `IsInRange` — inclusive bounds + +```csharp +using Wolfgang.Extensions.IComparable; + +DateTime today = DateTime.UtcNow.Date; +DateTime quarterStart = new DateTime(2026, 4, 1); +DateTime quarterEnd = new DateTime(2026, 6, 30); + +bool inQ2 = today.IsInRange(quarterStart, quarterEnd); // true on any Q2 date, + // including the boundary days +``` + +### Works with any `IComparable` + +```csharp +using Wolfgang.Extensions.IComparable; + +// string +"banana".IsInRange("apple", "cherry"); // true — lexicographic ordering + +// custom type — just implement IComparable +public record Money(decimal Amount, string Currency) : IComparable +{ + public int CompareTo(Money? other) => Amount.CompareTo(other?.Amount ?? 0m); +} + +var price = new Money(19.99m, "USD"); +price.IsBetween(new Money(10m, "USD"), new Money(50m, "USD")); // true +``` + +### Null safety + +```csharp +string? maybeNull = null; +maybeNull.IsBetween("a", "z"); // throws ArgumentNullException("value") +``` + +--- + +## 🎯 Target frameworks + +Multi-targeted to keep both modern .NET projects and long-tail .NET Framework consumers covered: + +- `net462` +- `netstandard2.0` +- `net8.0` +- `net10.0` From 2e12ac993520d5e892fdbc7329f6b221b45cd279 Mon Sep 17 00:00:00 2001 From: Chris Wolfgang <210299580+Chris-Wolfgang@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:53:08 -0400 Subject: [PATCH 2/2] Address PR #131 review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two Copilot findings on the README rewrite — both valid. 1. README.md L91 — Money example's CompareTo violated the IComparable contract for null. The contract says any non-null value > null (CompareTo returns positive). The old code 'Amount.CompareTo(other?.Amount ?? 0m)' returned negative for negative Amount (since Amount.CompareTo(0m) is negative when Amount<0). Replaced with an explicit null check returning 1, and left a comment explaining why. 2. README.md L102 — null-safety snippet would trigger a nullable warning (CS8604) when copied into a consumer's project with enable, because IsBetween's T is non-null and maybeNull is string?. Added the null-forgiving operator '!' and a comment so the snippet copy-pastes warning-free while still demonstrating the runtime ArgumentNullException. --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6fd5965..a67fbe7 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,12 @@ using Wolfgang.Extensions.IComparable; // custom type — just implement IComparable public record Money(decimal Amount, string Currency) : IComparable { - public int CompareTo(Money? other) => Amount.CompareTo(other?.Amount ?? 0m); + public int CompareTo(Money? other) => + // Per the IComparable contract, any non-null value is + // greater than null. Returning Amount.CompareTo(other?.Amount ?? 0m) + // would be wrong for negative Amount — a negative Money would + // compare as "less than" null instead of greater. + other is null ? 1 : Amount.CompareTo(other.Amount); } var price = new Money(19.99m, "USD"); @@ -99,7 +104,10 @@ price.IsBetween(new Money(10m, "USD"), new Money(50m, "USD")); // true ```csharp string? maybeNull = null; -maybeNull.IsBetween("a", "z"); // throws ArgumentNullException("value") +// The `!` keeps the example warning-free under enable; +// IsBetween's T is non-null, so without it the compiler complains. The +// runtime behaviour we're demonstrating is still the same: +maybeNull!.IsBetween("a", "z"); // throws ArgumentNullException("value") ``` ---