In multiple projects I've missed Range implementations for Enums and especially TimeSpans, but couldn't quite find anything that works.
So instead of multiple implementation of the same thing, but with a different underlying Type this project attempts to provide a API similar to the existing, but with whatever primitive the user desires.
This project provides a generic implementation of System.Range and System.Index constraint to primitive Types (such as int, char, and double), as well as Enum, and TimeSpan.
Primitives
| Integer | Floating-point |
|---|---|
sbyte |
float |
byte |
double |
short |
decimal |
ushort |
|
int |
|
uint |
|
long |
|
ulong |
Others
| Type | Comment |
|---|---|
TimeSpan |
Handled as long by TimeSpan.Ticks |
Enum |
Arithmetic based on underlying type. |
Install the NuGet package
dotnet add package GenericRangeSystem.Range and System.Index can be explicitly cast to Range<T>.
Range range = 0..^1;
Range<long> longRange = (Range<long>)range;This is only possible for IConvertible primitives, but not for TimeSpan, and Enum types.
Range range = 0..^1;
Range<Foo> fooRange = (Range<Foo>)range; // Throws InvalidCastException
Range<TimeSpan> timeRange = (Range<TimeSpan>)range; // Throws InvalidCastExceptionIn order to obtain a System.Range from a Range<T> the ToRange<T>() function can be used.
Range<int> intRange = new(2, true, 1, true); // ^2..^1
Range range = intRange.ToRange();Analogous to the System.Range -> Range<T> cast this only work for IConvertible primitives.
Range<Foo> fooRange = (Foo.One, Foo.Eight); // Implicit cast from ValueTuple<T,T> to Range<T>
Range range = fooRange.ToRange(); // Throws InvalidCastExceptionTo obtain the absolute index - the so called offset - from an Index<T> or Range<T> the GetOffset() and GetLengthAndOffset() methods can be used.
Range<double> doubleRange = new(12.04, 4, true); // 12.04..^4
double startOffset = doubleRange.Start.GetOffset(100.0); // = 12.04
(double offset, double length) = doubleRange.GetOffsetAndLength(100.0); // = (12.04, 96)Computes a bitmask (long) form the Range<Enum>.
Range<Foo> rangeAll = new(Foo.None, Foo.None, true);
Foo mask = (Foo)rangeAll.Mask(Foo.Eight);
Assert.AreEqual(Foo.All, mask);This example uses the following Enum.
public enum Foo : byte
{
None = 0,
One = 1,
Two = 2,
Three = 4,
Four = 8,
Five = 16,
Six = 32,
Seven = 64,
Eight = 128,
All = 255
}In this enum the bit-flag with the highest bit is Foo.Eight, but the highest value is Foo.All. That mean that if we call Mask without a maximum of Foo.Eight the maximum wont be a bit-flag, but instead the bit-mask Foo.All which will break the algorithm.
I could workaround this by masking the highest bit and thus filtering all masks from flags, but that would create a whole lot of different problems.
For all Enums that are not exclusively bit-flags use the maximum overload to ensure the function returns the correct result.
Almost all methods have an overload omitting the length argument required to resolve a index that IsFromEnd. Such methods Debug.Assert that all indices are !IsFromEnd.
For most cases wether an index IsFromEnd is determined at compile-time, thats how I justify Asserting instead of throwing.
In any edge cases; its generally safer to pick the overload with length.
Read the auto-generated doc file here.