8

We have lots of code that has “min” and “max” values for things like prices, profit, costs etc. At present these are passed as two parameters to methods and often have different properties/methods to retrieve them.

I have seen a 101 custom classes to store ranges of values in different code bases over the past few decades, before I create yet another such class, I wish to confirm that the .NET framework these days don’t have such a class built in somewhere.

(I know how to create my own class if needed, but we have too many wheels in this world already for me to just invent anther one on a whim)

Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317

4 Answers4

6

That's correct, before 2020, there was no built-in class in C# or the BCL for ranges. However, there is TimeSpan in the BCL for representing time spans, which you can compose additionally with a DateTime to represent a range of times.

Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317
jason
  • 236,483
  • 35
  • 423
  • 525
  • I got two unexplained downvotes as well. Quite annoying. +1 from me to compensate. – Steven Feb 11 '13 at 10:36
  • 1
    I guess because .Net actually has one, it's Enumerable class in the System.Core dll, so it's built-in and has a few Range APIs. – Arman Jan 16 '14 at 10:46
  • @Jason, the one in FCL is just a range of numbers but it's there and is very useful when you need some basic range to build your own stuff around. I'm guessing why the reply got a couple of downvotes. But, hands down, it was not me. I'm just guessing. – Arman Jan 23 '14 at 15:03
  • @Jason. Yes, it is. See https://msdn.microsoft.com/en-us/library/system.linq.enumerable.range(v=vs.100).aspx – sovemp Apr 10 '15 at 02:13
  • 1
    @sovemp: No, that is for producing a sequence over a given range, that is not the same thing as the ADT range that lets you test for membership, etc. – jason Apr 22 '15 at 01:03
  • @ArmanMcHitaryan: It's not the same thing as the poster is asking for. Please see my previous [comment](http://stackoverflow.com/questions/10172800/is-there-a-standard-class-to-represent-a-range-in-net/10172816?noredirect=1#comment47701556_10172816) to [sovemp](http://stackoverflow.com/users/713865/sovemp). – jason Apr 22 '15 at 01:04
6

AFAIK there is no such thing in .NET. Would be interesting though, to come up with a generic implementation.

Building a generic BCL quality range type is a lot of work, but it might look something like this:

public enum RangeBoundaryType
{
    Inclusive = 0,
    Exclusive
}

public struct Range<T> : IComparable<Range<T>>, IEquatable<Range<T>>
    where T : struct, IComparable<T>
{
    public Range(T min, T max) : 
        this(min, RangeBoundaryType.Inclusive, 
            max, RangeBoundaryType.Inclusive)
    {
    }

    public Range(T min, RangeBoundaryType minBoundary,
        T max, RangeBoundaryType maxBoundary)
    {
        this.Min = min;
        this.Max = max;
        this.MinBoundary = minBoundary;
        this.MaxBoundary = maxBoundary;
    }

    public T Min { get; private set; }
    public T Max { get; private set; }
    public RangeBoundaryType MinBoundary { get; private set; }
    public RangeBoundaryType MaxBoundary { get; private set; }

    public bool Contains(Range<T> other)
    {
        // TODO
    }

    public bool OverlapsWith(Range<T> other)
    {
        // TODO
    }

    public override string ToString()
    {
        return string.Format("Min: {0} {1}, Max: {2} {3}",
            this.Min, this.MinBoundary, this.Max, this.MaxBoundary);
    }

    public override int GetHashCode()
    {
        return this.Min.GetHashCode() << 256 ^ this.Max.GetHashCode();
    }

    public bool Equals(Range<T> other)
    {
        return
            this.Min.CompareTo(other.Min) == 0 &&
            this.Max.CompareTo(other.Max) == 0 &&
            this.MinBoundary == other.MinBoundary &&
            this.MaxBoundary == other.MaxBoundary;
    }

    public static bool operator ==(Range<T> left, Range<T> right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(Range<T> left, Range<T> right)
    {
        return !left.Equals(right);
    }

    public int CompareTo(Range<T> other)
    {
        if (this.Min.CompareTo(other.Min) != 0)
        {
            return this.Min.CompareTo(other.Min);
        }

        if (this.Max.CompareTo(other.Max) != 0)
        {
            this.Max.CompareTo(other.Max);
        }

        if (this.MinBoundary != other.MinBoundary)
        {
            return this.MinBoundary.CompareTo(other.Min);
        }

        if (this.MaxBoundary != other.MaxBoundary)
        {
            return this.MaxBoundary.CompareTo(other.MaxBoundary);
        }

        return 0;
    }
}
Steven
  • 166,672
  • 24
  • 332
  • 435
  • Well I haven't downvoted you but the thing I don't like in here, is enforcing T to be struct? Why such a constraint? Another thing is that the range itself shouldn;t be struct. Struct is good when it occupies less memory than a pointer (then it's efficient - copying). This class obviously takes more than 4 or 8 bytes. – dzendras May 12 '12 at 00:49
  • @dzendras: I do agree with your comments. The idea was to give the type value type semantics, so i picked `struct`, but this doesn't have to be. Note that the framework design guidelines advice value types to be smaller than 16 bytes, so not 8 or 4, but the range can easily become 16 bytes or bigger, for instance when using a `Range`. – Steven May 12 '12 at 01:40
2

I've started to make my own.

public class Range<T> where T : IComparable
{
    private readonly T start;

    private readonly T end;

    public Range(T start, T end)
    {
        if (start.CompareTo(end) < 0)
        {
            this.start = start;
            this.end = end;
        }
        else
        {
            this.start = end;
            this.end = start;
        }
    }

    public T Start
    {
        get
        {
            return this.start;
        }
    }

    public T End
    {
        get
        {
            return this.end;
        }
    }

    public static bool Intersect(Range<T> a, Range<T> b)
    {
        return !(b.Start.CompareTo(a.End) > 0 || a.Start.CompareTo(b.End) > 0);
    }

    public bool Intersect(Range<T> other)
    {
        return Intersect(this, other);
    }
}
Jodrell
  • 34,946
  • 5
  • 87
  • 124
2

This just changed with .Net Core 3.0 see System.Range.
C# 8 also has language support for creating ranges.

See also the "What is Range and Index types in c# 8?" Stackoverflow Question.

Notes these only support ranges of integers, and there is no support for ranges of double or floats.

Sp3ct3R
  • 715
  • 1
  • 12
  • 24
RX_View
  • 78
  • 1
  • 8