Interval<T> Struct
Definition
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
TThe numeric type used for the interval's endpoints.
- Implements
-
IEquatable<Interval<T>>
- 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
lowerTThe lower endpoint of the interval.
upperTThe upper endpoint of the interval.
lowerInclusivebooltrue when
loweris part of the interval (a closed lower endpoint); false when it is excluded (an open lower endpoint).upperInclusivebooltrue when
upperis 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
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
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 ofTcan satisfy both endpoint constraints.[5, 5]is non-empty and represents the single point5; 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
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
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
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
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
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
lowerTThe 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
upperTThe 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
lowerTThe lower endpoint.
upperTThe 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
lowerTThe lower endpoint (included).
upperTThe 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
otherInterval<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
valueTThe value to test for membership.
Returns
- bool
true when the interval is non-empty and
valuefalls 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
otherInterval<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 whenotheris disjoint or trims one side; and two disjoint intervals whenotherlies 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
otherInterval<T>The interval to compare against.
Returns
Equals(object?)
Determines whether this interval equals the boxed obj reference.
public override bool Equals(object? obj)
Parameters
objobjectThe object to compare against.
Returns
- bool
true when
objis 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
lowerTThe 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
otherInterval<T>The interval to intersect with.
Returns
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
upperTThe 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
lowerTThe lower endpoint.
upperTThe 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
lowerTThe lower endpoint (excluded).
upperTThe 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
otherInterval<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
sstringThe text to parse - for example
"[1, 5)","(0, 1)", or"∅".
Returns
- Interval<T>
The parsed interval.
Exceptions
- ArgumentNullException
Thrown when
sis null.- FormatException
Thrown when
sis 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
valueTThe 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
otherInterval<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
formatstringA 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
formatstringA format specifier accepted by
T.formatProviderIFormatProviderThe culture used to render each endpoint.
Returns
- string
The interval text with each endpoint formatted by
formatunderformatProvider.
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
utf8DestinationSpan<byte>The span that receives the formatted UTF-8 bytes.
bytesWrittenintWhen this method returns, contains the number of bytes written.
formatReadOnlySpan<char>The endpoint format specifier.
providerIFormatProviderThe culture used to render each endpoint.
Returns
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
destinationSpan<char>The span that receives the formatted characters.
charsWrittenintWhen this method returns, contains the number of characters written.
formatReadOnlySpan<char>The endpoint format specifier.
providerIFormatProviderThe culture used to render each endpoint.
Returns
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
sstringThe text to parse.
resultInterval<T>When this method returns true, the parsed interval; otherwise the default value.
Returns
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
otherInterval<T>The interval to union with.
resultInterval<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
Returns
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
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
Returns
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
Returns
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
utf8TextReadOnlySpan<byte>The UTF-8 encoded text to parse.
providerIFormatProviderThe culture used to parse each endpoint.
Returns
- Interval<T>
The parsed interval.
Exceptions
- FormatException
utf8Textis 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
sReadOnlySpan<char>The text to parse - for example
"[1, 5)","(0, 1)", or"∅".providerIFormatProviderThe culture used to parse each endpoint.
Returns
- Interval<T>
The parsed interval.
Exceptions
- FormatException
Thrown when
sis 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
sstringThe text to parse - for example
"[1, 5)","(0, 1)", or"∅".providerIFormatProviderThe 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
sis null.- FormatException
Thrown when
sis 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
utf8TextReadOnlySpan<byte>The UTF-8 encoded text to parse.
providerIFormatProviderThe culture used to parse each endpoint.
resultInterval<T>When this method returns true, the parsed interval; otherwise the default value.
Returns
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
sReadOnlySpan<char>The text to parse.
providerIFormatProviderThe culture used to parse each endpoint.
resultInterval<T>When this method returns true, the parsed interval; otherwise the default value.
Returns
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
sstringThe text to parse.
providerIFormatProviderThe culture used to parse each endpoint.
resultInterval<T>When this method returns true, the parsed interval; otherwise the default value.
Returns
Applies to
| Product | Versions |
|---|---|
| .NET | 8, 10 |