Table of Contents

Interval<T> Struct

Definition

Namespace
Bodu.Numerics
Assembly
Bodu.Numerics.dll
Package
Bodu.Numerics 1.0.0
Source
Interval{T}.Factories.cs

Represents an immutable, bounded interval over any INumber<TSelf> type, with independent open or closed endpoints on each side.

public readonly struct Interval<T> : ISpanFormattable, IFormattable, IUtf8SpanFormattable, IEquatable<Interval<T>>, ISpanParsable<Interval<T>>, IParsable<Interval<T>>, IUtf8SpanParsable<Interval<T>> where T : INumber<T>

Type Parameters

T

The numeric type used for the interval's endpoints.

Implements
Inherited Members
Extension Methods

Examples

using Bodu.Numerics;

// Scheduling windows - closed-open at the end keeps adjacent slots disjoint.
var morning = Interval<int>.ClosedOpen(9, 12);
var meeting = Interval<int>.ClosedOpen(11, 13);
bool clash   = morning.Overlaps(meeting);            // True
var conflict = morning.Intersect(meeting);           // [11, 12)

// Validation predicate - a percentage in [0, 100].
var percentage = Interval<double>.Closed(0.0, 100.0);
bool ok = percentage.Contains(99.5);                 // True

// Adjacent half-open ranges merge into a single contiguous interval.
var q1 = Interval<int>.ClosedOpen(0, 90);
var q2 = Interval<int>.ClosedOpen(90, 181);
q1.TryUnion(q2, out var firstHalf);                  // firstHalf = [0, 181)

Remarks

Interval<T> is the value-typed building block for working with numeric ranges as first-class data. Rather than encoding a range as a (min, max) tuple or a pair of bool predicates and threading the inclusivity convention through every call site, an interval carries both endpoints and their inclusivity in a single immutable value, with set algebra - membership, containment, intersection, union, overlap, adjacency - defined on the type. Reach for it whenever the range itself is a value the code passes around, validates against, intersects, merges, or persists. Interval<T> is more general than System.Range (which is integer-only and always [start, end)) and safer than untyped tuples or (bool, bool) flags, where the inclusivity convention lives in comments rather than in the value.

The generic-math constraint on T means the same code can carry an integer scheduling window, a double percentage range, a decimal price band, or a BigInteger arbitrary-magnitude bound; any type implementing INumber<TSelf> is a valid endpoint type, including consumer-defined numeric types.

An interval is the set of values x such that Lower ≤ x ≤ Upper (closed-closed), Lower < x < Upper (open-open), Lower ≤ x < Upper (closed-open), or Lower < x ≤ Upper (open-closed). Endpoint inclusion is independent for the two sides and is preserved through every operation defined on the type. The closed-open shape - [a, b) - is the most common in programming contexts because adjacent half-open intervals partition a span with no overlap and no gap, matching the conventions of System.Range, LINQ's Enumerable.Range, and slice iterators.

An interval is empty when its bounds do not admit any value - either Lower > Upper, or Lower ≡ Upper with at least one endpoint open. All empty intervals are equal to Empty by Equals(T), and a structural pattern-match against an empty instance always returns the same canonical projection regardless of how the empty value was constructed. The default-constructed Interval<T> is empty, so a default(Interval<T>) field reads as the empty interval rather than as a malformed value.

Unbounded intervals - those with no lower limit, no upper limit, or neither, written (-∞, b], [a, +∞), or (-∞, +∞) - are representable through the AtLeast(T), GreaterThan(T), AtMost(T), LessThan(T), and All factories. An unbounded endpoint carries no concrete value and is always open (infinity is never a member); LowerUnbounded and UpperUnbounded report which sides, if any, are unbounded, and IsBounded is true only when both sides are finite. An unbounded interval is never empty, and Length is undefined for it.

Instances are immutable, structurally equatable, and safe to share. They format using ISO 31-11 interval notation (square brackets for closed endpoints, round brackets for open endpoints, and the U+2205 EMPTY SET glyph for the empty interval) via ToString(), and parse the same notation through Parse(string, IFormatProvider?) and the TryParse(string?, IFormatProvider?, out Interval<T>) family.

Constructors

Interval(T, T, bool, bool)

Initializes a new instance of the Interval<T> struct from a lower and upper endpoint and explicit inclusion flags for each side.

public Interval(T lower, T upper, bool lowerInclusive, bool upperInclusive)

Parameters

lower T

The lower endpoint of the interval.

upper T

The upper endpoint of the interval.

lowerInclusive bool

true when lower is part of the interval (a closed lower endpoint); false when it is excluded (an open lower endpoint).

upperInclusive bool

true when upper is part of the interval (a closed upper endpoint); false when it is excluded (an open upper endpoint).

Remarks

The constructor accepts any combination of endpoints, including lower equal to or greater than upper. When the supplied bounds do not admit any value, the resulting instance is empty and compares equal to Empty. The original bounds remain readable via Lower and Upper for diagnostic inspection, but participate only as the representation of Empty in set operations.

Properties

All

Gets the unbounded interval (-∞, +∞) - the interval that contains every value of T.

public static Interval<T> All { get; }

Property Value

Interval<T>

An interval with both sides unbounded; IsBounded is false and Contains(T) is true for every value.

Examples

var all = Interval<double>.All;
all.Contains(double.MinValue);   // True
all.ToString();                  // "(-∞, +∞)"

Empty

Gets the canonical empty interval - the interval that contains no values. Equal by Equals(T) to every other empty Interval<T> over the same T.

public static Interval<T> Empty { get; }

Property Value

Interval<T>

An Interval<T> whose IsEmpty property is true.

Examples

var none = Interval<int>.Empty;
none.IsEmpty;                    // True
none.ToString();                 // "∅"

// Any inverted-bounds or equal-bounds-with-open-endpoint interval compares equal to Empty.
var inverted = new Interval<int>(5, 1, true, true);
var collapsed = new Interval<int>(0, 0, false, false);
none == inverted;                // True
none == collapsed;               // True

Remarks

Use Empty as the neutral element of the interval set algebra: Intersect(Interval<T>) returns it when two operands share no values, and TryUnion(Interval<T>, out Interval<T>) treats it as the identity. The default-constructed Interval<T> compares equal to Empty, so a field or local that was never assigned reads as the empty interval rather than as a malformed value.

IsBounded

Gets a value indicating whether both endpoints are finite - the interval has a concrete lower and upper limit.

public bool IsBounded { get; }

Property Value

bool

true when neither side is unbounded; otherwise false.

IsDegenerate

Gets a value indicating whether the interval represents a single point - a closed-closed interval whose lower and upper endpoints are equal.

public bool IsDegenerate { get; }

Property Value

bool

true when Lower equals Upper and both LowerInclusive and UpperInclusive are true; otherwise false.

IsEmpty

Gets a value indicating whether the interval contains no values.

public bool IsEmpty { get; }

Property Value

bool

true when the interval is empty; otherwise false.

Examples

Interval<int>.Closed(1, 5).IsEmpty;     // False - [1, 5] holds values
Interval<int>.Open(5, 5).IsEmpty;       // True  - (5, 5) admits no value
Interval<int>.Closed(5, 1).IsEmpty;     // True  - inverted bounds
Interval<int>.Closed(5, 5).IsEmpty;     // False - the single point 5

Remarks

An interval is empty when its bounds cannot admit any value. Two cases produce an empty interval:

  • Lower is strictly greater than Upper - the bounds are inverted.
  • Lower equals Upper and at least one endpoint is open - for example (5, 5], [5, 5), and (5, 5) are all empty because no value of T can satisfy both endpoint constraints. [5, 5] is non-empty and represents the single point 5; see IsDegenerate.

Length

Gets the algebraic length of the interval - the difference between its upper and lower endpoints.

public T Length { get; }

Property Value

T

The non-negative length of the interval, or Zero when empty.

Remarks

For non-empty intervals, the length is computed as Upper - Lower regardless of endpoint inclusion. This matches the Lebesgue measure for continuous numeric types (double, decimal): the measure of [1, 2], (1, 2), [1, 2), and (1, 2] is the same value 1.

For integer types, callers wanting the count of integers contained in the interval should compute it directly from Lower, Upper, LowerInclusive, and UpperInclusive - endpoint inclusion matters for that semantic, and this property does not model it.

For empty intervals, the length is Zero. For unbounded intervals the length is infinite and not representable in T, so this property throws.

Exceptions

InvalidOperationException

The interval is unbounded (IsBounded is false).

Lower

Gets the lower endpoint of the interval.

public T Lower { get; }

Property Value

T

The lower endpoint passed to the constructor or factory method.

LowerInclusive

Gets a value indicating whether the lower endpoint is part of the interval.

public bool LowerInclusive { get; }

Property Value

bool

true when the interval is closed on the lower side (i.e. [Lower, ...); false when open (i.e. (Lower, ...).

LowerUnbounded

Gets a value indicating whether the lower side is unbounded - the interval extends to -∞ with no finite lower limit.

public bool LowerUnbounded { get; }

Property Value

bool

true when the interval is lower-unbounded (i.e. (-∞, ...); otherwise false.

Upper

Gets the upper endpoint of the interval.

public T Upper { get; }

Property Value

T

The upper endpoint passed to the constructor or factory method.

UpperInclusive

Gets a value indicating whether the upper endpoint is part of the interval.

public bool UpperInclusive { get; }

Property Value

bool

true when the interval is closed on the upper side (i.e. ..., Upper]); false when open (i.e. ..., Upper)).

UpperUnbounded

Gets a value indicating whether the upper side is unbounded - the interval extends to +∞ with no finite upper limit.

public bool UpperUnbounded { get; }

Property Value

bool

true when the interval is upper-unbounded (i.e. ..., +∞)); otherwise false.

Methods

AtLeast(T)

Creates the lower-bounded interval [lower, +∞) - every value greater than or equal to lower.

public static Interval<T> AtLeast(T lower)

Parameters

lower T

The inclusive lower endpoint.

Returns

Interval<T>

A closed-below, upper-unbounded interval.

Examples

var nonNegative = Interval<double>.AtLeast(0.0);   // [0, +∞)
nonNegative.Contains(0.0);                         // True
nonNegative.ToString();                            // "[0, +∞)"

AtMost(T)

Creates the upper-bounded interval (-∞, upper] - every value less than or equal to upper.

public static Interval<T> AtMost(T upper)

Parameters

upper T

The inclusive upper endpoint.

Returns

Interval<T>

A lower-unbounded, closed-above interval.

Examples

var capped = Interval<double>.AtMost(5.0);   // (-∞, 5]
capped.Contains(5.0);                        // True - closed upper
capped.ToString();                           // "(-∞, 5]"

Closed(T, T)

Creates a closed-closed interval - [lower, upper] - that includes both endpoints.

public static Interval<T> Closed(T lower, T upper)

Parameters

lower T

The lower endpoint.

upper T

The upper endpoint.

Returns

Interval<T>

A closed-closed interval over the supplied bounds.

Examples

var percentage = Interval<int>.Closed(0, 100);   // [0, 100]
percentage.Contains(0);                          // True - closed lower
percentage.Contains(100);                        // True - closed upper
percentage.ToString();                           // "[0, 100]"

// Inverted bounds collapse to Empty.
Interval<int>.Closed(10, 5).IsEmpty;             // True

Remarks

The closed-closed shape - also called inclusive - is the natural choice for ranges where both boundary values are valid members of the set: a percentage in [0, 100], a die roll in [1, 6], a thermometer reading in [-273.15, +∞). Prefer ClosedOpen(T, T) for spans, slices, and scheduling windows where adjacent ranges should partition cleanly.

When lower is greater than upper, the returned interval is empty. When the two endpoints are equal, the returned interval is a degenerate single-point interval (see Singleton(T)).

ClosedOpen(T, T)

Creates a closed-open interval - [lower, upper) - that includes the lower endpoint and excludes the upper endpoint.

public static Interval<T> ClosedOpen(T lower, T upper)

Parameters

lower T

The lower endpoint (included).

upper T

The upper endpoint (excluded).

Returns

Interval<T>

A closed-open interval over the supplied bounds.

Examples

var window = Interval<int>.ClosedOpen(0, 100);   // [0, 100)
window.Contains(0);                              // True  - closed lower
window.Contains(99);                             // True
window.Contains(100);                            // False - open upper

// Adjacent half-open windows merge with no overlap and no gap.
var q1 = Interval<int>.ClosedOpen(0, 90);
var q2 = Interval<int>.ClosedOpen(90, 181);
q1.TryUnion(q2, out var firstHalf);              // firstHalf = [0, 181)

Remarks

The closed-open shape is the most common in programming contexts: a range starting at an inclusive lower bound and ending before an exclusive upper bound matches the conventions of System.Range, LINQ's Enumerable.Range, and most iterator protocols. Adjacent closed-open intervals partition a span cleanly - [0, 90) and [90, 181) together cover exactly [0, 181) with no overlap and no gap - so this shape is the default choice for scheduling windows, bucket boundaries, and time slots.

Contains(Interval<T>)

Determines whether this interval fully contains other - every value of other is also a value of this interval.

public bool Contains(Interval<T> other)

Parameters

other Interval<T>

The interval to test for containment.

Returns

bool

true when this interval is a superset of other; otherwise false. The empty interval is a subset of every interval, so any interval contains the empty interval; only the empty interval contains the empty interval as a non-empty member.

Examples

var outer = Interval<int>.Closed(0, 10);

outer.Contains(Interval<int>.Closed(2, 8));        // True - strict subset
outer.Contains(Interval<int>.Closed(0, 10));       // True - equal sets
outer.Contains(Interval<int>.Closed(2, 11));       // False - exceeds upper
outer.Contains(Interval<int>.Empty);               // True - ∅ ⊆ every set

// Endpoint inclusivity is honored: an open lower fits inside a closed lower at the same value.
var closed = Interval<int>.Closed(0, 10);          // [0, 10]
var open   = Interval<int>.Open(0, 10);            // (0, 10)
closed.Contains(open);                             // True
open.Contains(closed);                             // False - closed includes 0 and 10

Contains(T)

Determines whether value lies within the interval, honoring the inclusivity of each endpoint.

public bool Contains(T value)

Parameters

value T

The value to test for membership.

Returns

bool

true when the interval is non-empty and value falls between the endpoints under the configured inclusivity rules; otherwise false. An empty interval contains no value, including itself.

Examples

var window = Interval<int>.ClosedOpen(1, 5);   // [1, 5)
window.Contains(1);                            // True  - closed lower
window.Contains(4);                            // True  - interior
window.Contains(5);                            // False - open upper
window.Contains(0);                            // False - outside

Interval<int>.Empty.Contains(0);               // False - the empty interval contains nothing

Difference(Interval<T>)

Returns this interval with the values of other removed - the set difference this \ other - as zero, one, or two disjoint intervals.

public IntervalPair<T> Difference(Interval<T> other)

Parameters

other Interval<T>

The interval whose values are removed from this one.

Returns

IntervalPair<T>

An IntervalPair<T> holding the remainder: empty when this interval is empty or wholly covered by other; a single interval when other is disjoint or trims one side; and two disjoint intervals when other lies strictly inside this interval.

Examples

Interval<int>.Closed(0, 10).Difference(Interval<int>.Closed(3, 5));   // [0, 3) ∪ (5, 10]
Interval<int>.Closed(0, 10).Difference(Interval<int>.Closed(8, 20));  // [0, 8)
Interval<double>.All.Difference(Interval<double>.Closed(3, 5));       // (-∞, 3) ∪ (5, +∞)

Remarks

The endpoint at each cut flips inclusivity: removing a closed bound leaves an open bound on the remainder, and vice versa. Unbounded operands are handled naturally - for example the difference of All and a finite interval yields the two half-lines around it.

Equals(Interval<T>)

Determines whether this interval equals other as a set - same lower endpoint, same upper endpoint, and matching inclusivity on each side. Any two empty intervals are equal regardless of the bounds used to construct them.

public bool Equals(Interval<T> other)

Parameters

other Interval<T>

The interval to compare against.

Returns

bool

true when the two intervals describe the same set of values; otherwise false.

Equals(object?)

Determines whether this interval equals the boxed obj reference.

public override bool Equals(object? obj)

Parameters

obj object

The object to compare against.

Returns

bool

true when obj is an Interval<T> equal to this one; otherwise false.

GetHashCode()

Returns a hash code consistent with Equals(Interval<T>). All empty intervals share the same hash; non-empty intervals hash on their endpoints and inclusivity flags.

public override int GetHashCode()

Returns

int

A 32-bit hash code suitable for use in hash-based collections.

GreaterThan(T)

Creates the lower-bounded interval (lower, +∞) - every value strictly greater than lower.

public static Interval<T> GreaterThan(T lower)

Parameters

lower T

The exclusive lower endpoint.

Returns

Interval<T>

An open-below, upper-unbounded interval.

Examples

var positive = Interval<double>.GreaterThan(0.0);   // (0, +∞)
positive.Contains(0.0);                             // False - open lower
positive.ToString();                                // "(0, +∞)"

Intersect(Interval<T>)

Returns the intersection of this interval with other - the interval of values shared by both.

public Interval<T> Intersect(Interval<T> other)

Parameters

other Interval<T>

The interval to intersect with.

Returns

Interval<T>

The intersection interval, or Empty when the two intervals share no values.

Examples

Interval<int>.Closed(1, 5).Intersect(Interval<int>.Closed(3, 7));   // [3, 5]
Interval<int>.Closed(1, 3).Intersect(Interval<int>.Closed(5, 7));   // ∅ - disjoint

// On ties, the stricter (open) inclusivity wins.
var closed = Interval<int>.Closed(1, 5);   // [1, 5]
var open   = Interval<int>.Open(1, 5);     // (1, 5)
closed.Intersect(open);                    // (1, 5)

Remarks

On endpoint ties, the stricter (open) inclusivity wins so that the result is a true subset of both operands. For example, [1, 5] intersected with (1, 5) yields (1, 5).

LessThan(T)

Creates the upper-bounded interval (-∞, upper) - every value strictly less than upper.

public static Interval<T> LessThan(T upper)

Parameters

upper T

The exclusive upper endpoint.

Returns

Interval<T>

A lower-unbounded, open-above interval.

Examples

var belowFive = Interval<double>.LessThan(5.0);   // (-∞, 5)
belowFive.Contains(5.0);                          // False - open upper
belowFive.ToString();                             // "(-∞, 5)"

Open(T, T)

Creates an open-open interval - (lower, upper) - that excludes both endpoints.

public static Interval<T> Open(T lower, T upper)

Parameters

lower T

The lower endpoint.

upper T

The upper endpoint.

Returns

Interval<T>

An open-open interval over the supplied bounds.

Examples

var strictlyPositive = Interval<double>.Open(0.0, double.PositiveInfinity);  // (0, +∞)
strictlyPositive.Contains(0.0);                                              // False - open lower
strictlyPositive.Contains(1e-300);                                           // True

// Equal bounds with both endpoints open are empty.
Interval<int>.Open(5, 5).IsEmpty;                                            // True

Remarks

The open-open shape - also called exclusive - is the natural choice for strict inequalities such as 0 < rate < 1 or "between but not at" semantics where neither boundary value is itself a member.

When lower is greater than or equal to upper, the returned interval is empty - there is no value strictly between two equal or inverted bounds.

OpenClosed(T, T)

Creates an open-closed interval - (lower, upper] - that excludes the lower endpoint and includes the upper endpoint.

public static Interval<T> OpenClosed(T lower, T upper)

Parameters

lower T

The lower endpoint (excluded).

upper T

The upper endpoint (included).

Returns

Interval<T>

An open-closed interval over the supplied bounds.

Examples

// Billing tier: anything above $1,000 up to and including $10,000.
var tier = Interval<decimal>.OpenClosed(1000m, 10_000m);   // (1000, 10000]
tier.Contains(1000m);                                       // False - open lower
tier.Contains(10_000m);                                     // True  - closed upper
tier.ToString();                                            // "(1000, 10000]"

Remarks

The open-closed shape is the mirror image of ClosedOpen(T, T) and the natural choice for ranges expressed as "strictly greater than X, up to and including Y" - a billing tier above a threshold, a histogram bin that owns its upper edge, or a tax bracket that exits at one boundary and enters at the next.

Overlaps(Interval<T>)

Determines whether this interval shares any values with other.

public bool Overlaps(Interval<T> other)

Parameters

other Interval<T>

The interval to test for overlap.

Returns

bool

true when the two intervals share at least one value; otherwise false. An empty interval shares no values with any interval and is therefore never overlapping.

Examples

var a = Interval<int>.Closed(1, 5);
var b = Interval<int>.Closed(3, 7);
a.Overlaps(b);                                                     // True - share [3, 5]

// Touching at a boundary but not both including it: NOT overlapping.
Interval<int>.ClosedOpen(1, 5).Overlaps(Interval<int>.Closed(5, 10));   // False - neither holds 5 jointly
Interval<int>.OpenClosed(1, 5).Overlaps(Interval<int>.Closed(5, 10));   // True  - both include 5

Interval<int>.Closed(1, 2).Overlaps(Interval<int>.Closed(5, 6));   // False - disjoint

Remarks

Two intervals that touch but do not share any value - for example [1, 2) and [2, 3] - do not overlap, because no single value belongs to both. To test whether they are adjacent (touching), inspect the endpoints directly.

Parse(string)

Parses an interval from its ISO 31-11 bracket-notation string representation using the current culture.

public static Interval<T> Parse(string s)

Parameters

s string

The text to parse - for example "[1, 5)", "(0, 1)", or "∅".

Returns

Interval<T>

The parsed interval.

Exceptions

ArgumentNullException

Thrown when s is null.

FormatException

Thrown when s is not a valid interval representation.

Singleton(T)

Creates a degenerate interval that contains the single value value - equivalent to [value, value].

public static Interval<T> Singleton(T value)

Parameters

value T

The single value the interval contains.

Returns

Interval<T>

A closed-closed interval whose lower and upper endpoints both equal value.

Examples

var single = Interval<int>.Singleton(42);
single.Contains(42);                              // True
single.IsDegenerate;                              // True
single.IsEmpty;                                   // False
single.Length;                                    // 0
single.ToString();                                // "[42, 42]"

Remarks

A degenerate interval has algebraic Length of zero but is not empty: it contains exactly the one value its endpoints share. Use it as the identity for an "any-of" intersection sweep, as a single-value filter that composes uniformly with multi-value filters, or as the seed of a union that accumulates a span.

SymmetricDifference(Interval<T>)

Returns the symmetric difference of this interval and other - the values in exactly one of the two, (this \ other) ∪ (other \ this) - as zero, one, or two disjoint intervals.

public IntervalPair<T> SymmetricDifference(Interval<T> other)

Parameters

other Interval<T>

The interval to symmetric-difference with.

Returns

IntervalPair<T>

An IntervalPair<T> holding the values covered by exactly one operand.

Examples

Interval<int>.Closed(0, 5).SymmetricDifference(Interval<int>.Closed(3, 8));   // [0, 3) ∪ (5, 8]
Interval<int>.Closed(0, 5).SymmetricDifference(Interval<int>.Closed(0, 5));   // ∅ (equal sets)

ToString()

Returns the default string representation of this interval using ISO 31-11 bracket notation.

public override string ToString()

Returns

string

"[lower, upper]" for closed-closed intervals, "(lower, upper)" for open-open intervals, "[lower, upper)" for closed-open intervals, "(lower, upper]" for open-closed intervals, or "∅" (the empty-set glyph) for any empty interval.

Examples

Interval<int>.Closed(1, 5).ToString();        // "[1, 5]"
Interval<int>.ClosedOpen(1, 5).ToString();    // "[1, 5)"
Interval<int>.Open(0, 1).ToString();          // "(0, 1)"
Interval<int>.Empty.ToString();               // "∅"

// The format specifier is applied to each endpoint.
Interval<double>.Closed(1, 5).ToString("F2"); // "[1.00, 5.00]"

ToString(string?)

Returns a string representation of this interval using the endpoint format specifier format applied to Lower and Upper.

public string ToString(string? format)

Parameters

format string

A format specifier accepted by T (e.g. "F2", "N").

Returns

string

The interval text with each endpoint formatted by format.

ToString(string?, IFormatProvider?)

Returns a string representation of this interval using the supplied endpoint format and culture.

public string ToString(string? format, IFormatProvider? formatProvider)

Parameters

format string

A format specifier accepted by T.

formatProvider IFormatProvider

The culture used to render each endpoint.

Returns

string

The interval text with each endpoint formatted by format under formatProvider.

TryFormat(Span<byte>, out int, ReadOnlySpan<char>, IFormatProvider?)

Attempts to format this interval into the provided UTF-8 byte span.

public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider)

Parameters

utf8Destination Span<byte>

The span that receives the formatted UTF-8 bytes.

bytesWritten int

When this method returns, contains the number of bytes written.

format ReadOnlySpan<char>

The endpoint format specifier.

provider IFormatProvider

The culture used to render each endpoint.

Returns

bool

true when utf8Destination was large enough; otherwise false.

TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider?)

Attempts to format this interval into the provided character span.

public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)

Parameters

destination Span<char>

The span that receives the formatted characters.

charsWritten int

When this method returns, contains the number of characters written.

format ReadOnlySpan<char>

The endpoint format specifier.

provider IFormatProvider

The culture used to render each endpoint.

Returns

bool

true when destination was large enough; otherwise false.

TryParse(string?, out Interval<T>)

Attempts to parse an interval from its ISO 31-11 bracket-notation string representation using the current culture.

public static bool TryParse(string? s, out Interval<T> result)

Parameters

s string

The text to parse.

result Interval<T>

When this method returns true, the parsed interval; otherwise the default value.

Returns

bool

true when parsing succeeded; otherwise false.

TryUnion(Interval<T>, out Interval<T>)

Attempts to compute the union of this interval with other as a single contiguous interval.

public bool TryUnion(Interval<T> other, out Interval<T> result)

Parameters

other Interval<T>

The interval to union with.

result Interval<T>

When the method returns true, contains the contiguous union of the two intervals; otherwise Empty.

Returns

bool

true when the two intervals are either overlapping or adjacent (their union is a single contiguous interval); false when the intervals are disjoint and their union would require two separate intervals to represent.

Examples

// Adjacent - [1, 5) ∪ [5, 10] → [1, 10]
Interval<int>.ClosedOpen(1, 5).TryUnion(Interval<int>.Closed(5, 10), out var contiguous);
// contiguous == [1, 10], result == true

// Overlapping - [1, 5] ∪ [3, 7] → [1, 7]
Interval<int>.Closed(1, 5).TryUnion(Interval<int>.Closed(3, 7), out var merged);
// merged == [1, 7], result == true

// Disjoint - [1, 5) ∪ (5, 10] is not contiguous (no operand contains 5).
bool ok = Interval<int>.ClosedOpen(1, 5).TryUnion(Interval<int>.OpenClosed(5, 10), out _);
// ok == false

// Empty operand acts as identity.
Interval<int>.Empty.TryUnion(Interval<int>.Closed(1, 5), out var same);
// same == [1, 5], result == true

Remarks

Two intervals are adjacent when the upper endpoint of one equals the lower endpoint of the other and at least one of those endpoints is inclusive. For example, [1, 2) and [2, 3] are adjacent and union to [1, 3]; [1, 2) and (2, 3] are disjoint because the value 2 is in neither interval and the result would not be contiguous.

On endpoint ties, the looser (closed) inclusivity wins so that the result is a superset of either operand. Union with the empty interval is always defined: an empty operand leaves the other operand unchanged.

When the two intervals are disjoint with a true gap between them, the union would require two pieces to represent and this method returns false rather than synthesise a non-contiguous result. Callers that need a multi-piece result should accumulate the operands into a higher-level collection of intervals.

Operators

operator &(Interval<T>, Interval<T>)

Returns the intersection of two intervals - the values shared by both - as an operator alias for Intersect(Interval<T>).

public static Interval<T> operator &(Interval<T> left, Interval<T> right)

Parameters

left Interval<T>

The first interval.

right Interval<T>

The second interval.

Returns

Interval<T>

The intersection interval, or Empty when the two share no values.

Examples

var shared = Interval<int>.Closed(1, 5) & Interval<int>.Closed(3, 7);   // [3, 5]

operator |(Interval<T>, Interval<T>)

Returns the contiguous union of two intervals as an operator alias for TryUnion(Interval<T>, out Interval<T>), for operands whose union is a single interval.

public static Interval<T> operator |(Interval<T> left, Interval<T> right)

Parameters

left Interval<T>

The first interval.

right Interval<T>

The second interval.

Returns

Interval<T>

The single contiguous interval covering both operands.

Examples

var merged = Interval<int>.ClosedOpen(1, 5) | Interval<int>.Closed(5, 10);   // [1, 10]

Exceptions

InvalidOperationException

The operands are disjoint and non-adjacent, so their union is not a single contiguous interval. Use Difference(Interval<T>) or accumulate the pieces when a multi-interval result is possible.

operator ==(Interval<T>, Interval<T>)

Determines whether two intervals are equal as sets. See Equals(Interval<T>).

public static bool operator ==(Interval<T> left, Interval<T> right)

Parameters

left Interval<T>

The first interval.

right Interval<T>

The second interval.

Returns

bool

true when the intervals are equal; otherwise false.

operator !=(Interval<T>, Interval<T>)

Determines whether two intervals are not equal as sets. See Equals(Interval<T>).

public static bool operator !=(Interval<T> left, Interval<T> right)

Parameters

left Interval<T>

The first interval.

right Interval<T>

The second interval.

Returns

bool

true when the intervals differ; otherwise false.

Explicit Interface Implementations

Parse(ReadOnlySpan<byte>, IFormatProvider?)

Parses a UTF-8 byte span containing ISO 31-11 bracket notation into an Interval<T>.

static Interval<T> Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider)

Parameters

utf8Text ReadOnlySpan<byte>

The UTF-8 encoded text to parse.

provider IFormatProvider

The culture used to parse each endpoint.

Returns

Interval<T>

The parsed interval.

Exceptions

FormatException

utf8Text is not a valid interval representation.

Parse(ReadOnlySpan<char>, IFormatProvider?)

Parses an interval from its ISO 31-11 bracket-notation span representation.

static Interval<T> Parse(ReadOnlySpan<char> s, IFormatProvider? provider)

Parameters

s ReadOnlySpan<char>

The text to parse - for example "[1, 5)", "(0, 1)", or "∅".

provider IFormatProvider

The culture used to parse each endpoint.

Returns

Interval<T>

The parsed interval.

Exceptions

FormatException

Thrown when s is not a valid interval representation.

Parse(string, IFormatProvider?)

Parses an interval from its ISO 31-11 bracket-notation string representation.

static Interval<T> Parse(string s, IFormatProvider? provider)

Parameters

s string

The text to parse - for example "[1, 5)", "(0, 1)", or "∅".

provider IFormatProvider

The culture used to parse each endpoint.

Returns

Interval<T>

The parsed interval.

Examples

// ISO 31-11 bracket notation; the bracket style selects endpoint inclusivity.
var closedOpen = Interval<int>.Parse("[1, 5)", null);   // closed lower, open upper
var open = Interval<int>.Parse("(0, 1)", null);         // both endpoints excluded
var empty = Interval<int>.Parse("∅", null);             // the empty interval

// Round-trip: a parsed interval formats back to its bracket notation.
string text = closedOpen.ToString();                    // "[1, 5)"

if (!Interval<int>.TryParse("1..5", null, out var parsed))
{
    // reached - "1..5" is not ISO 31-11 notation
}

Exceptions

ArgumentNullException

Thrown when s is null.

FormatException

Thrown when s is not a valid interval representation.

TryParse(ReadOnlySpan<byte>, IFormatProvider?, out Interval<T>)

Attempts to parse a UTF-8 byte span containing ISO 31-11 bracket notation into an Interval<T>.

static bool TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, out Interval<T> result)

Parameters

utf8Text ReadOnlySpan<byte>

The UTF-8 encoded text to parse.

provider IFormatProvider

The culture used to parse each endpoint.

result Interval<T>

When this method returns true, the parsed interval; otherwise the default value.

Returns

bool

true when parsing succeeded; otherwise false.

TryParse(ReadOnlySpan<char>, IFormatProvider?, out Interval<T>)

Attempts to parse an interval from its ISO 31-11 bracket-notation span representation.

static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Interval<T> result)

Parameters

s ReadOnlySpan<char>

The text to parse.

provider IFormatProvider

The culture used to parse each endpoint.

result Interval<T>

When this method returns true, the parsed interval; otherwise the default value.

Returns

bool

true when parsing succeeded; otherwise false.

TryParse(string?, IFormatProvider?, out Interval<T>)

Attempts to parse an interval from its ISO 31-11 bracket-notation string representation.

static bool TryParse(string? s, IFormatProvider? provider, out Interval<T> result)

Parameters

s string

The text to parse.

provider IFormatProvider

The culture used to parse each endpoint.

result Interval<T>

When this method returns true, the parsed interval; otherwise the default value.

Returns

bool

true when parsing succeeded; otherwise false.

Applies to

ProductVersions
.NET8, 10