Modeling and computing intersection between continuous intervals I created the class ContinuousDimensionInterval, to model an interval of a continuous dimension interval, presented below.

namespace Minotaur.Math.Dimensions {
    using System;

    public sealed class ContinuousDimensionInterval: IDimensionInterval {

        public int DimensionIndex { get; }
        public readonly DimensionBound Start;
        public readonly DimensionBound End;

        public ContinuousDimensionInterval(
            int dimensionIndex,
            DimensionBound start, 
            DimensionBound end
            ) {
            if (dimensionIndex &lt; 0)
                throw new ArgumentOutOfRangeException(nameof(dimensionIndex) + " must be &gt;= 0.");

            DimensionIndex = dimensionIndex;
            Start = start;
            End = end;
        }

        public bool Contains(float value) {
            if (float.IsNaN(value))
                throw new ArgumentOutOfRangeException(nameof(value));

            if (value &gt; Start.Value &amp;&amp; value &lt; End.Value)
                return true;
            if (Start.IsInclusive &amp;&amp; value == Start.Value)
                return true;
            if (End.IsInclusive &amp;&amp; value == End.Value)
                return true;

            return false;
        }
    }
}


I felt the need to create a struct, DimensionBound, to represent the bounds of an interval, since they may be inclusive or exclusive.

namespace Minotaur.Math.Dimensions {
    using System;

    public readonly struct DimensionBound {
        public readonly float Value;
        public readonly bool IsInclusive;

        public DimensionBound(float value, bool isInclusive) {
            if (float.IsNaN(value))
                throw new ArgumentOutOfRangeException(nameof(value) + " can't be NaN.");

            Value = value;
            IsInclusive = isInclusive;
        }
    }
}


I'd like to receive some feedback on the implementation of both classes.


Should I disallow the construction of DimensionBound with
value=infinity and inclusive=true?
Is my decision to make DimensionBound a struct reasonable? It feels struct-like to me...
Should DimensionBound implement IEquatable&lt;DimensionBound&gt;?
Am I over-engineering the whole thing by creating a class to represent the bounds of an interval?


Here an example of how the class is used:

private static bool IntersectsContinuous(ContinuousDimensionInterval lhs, ContinuousDimensionInterval rhs) {
    // @Improve performance?
    // @Verify correctness
    var aStart= lhs.Start;
    var aEnd = lhs.End;

    var bStart = rhs.Start;
    var bEnd = rhs.End;

    if (aStart.Value &gt; bEnd.Value)
        return false;
    if (aStart.Value == bEnd.Value)
        return aStart.IsInclusive &amp;&amp; bEnd.IsInclusive;

    if (bStart.Value &gt; aEnd.Value)
        return false;
    if (bStart.Value == aEnd.Value)
        return bStart.IsInclusive &amp;&amp; aEnd.IsInclusive;

    return true;
}
Review


You are constrained to float which makes your class only usable for a limited number of scenarios. I would like to be able to work with int, double, DateTime, MyCustomEntity, ..
You should not need to care about special cases as unbounded or NaN. NaN should be filtered out beforehand, since it is unusable for comparison. Unbounded and bounded values should be compared by using the built-in interface IComparable.
Intersection of continuous data when 2 edges match requires both edges to be IsInclusive in your specification. I would argue that only one of them should require this flag. Adjacent items that meet each other form an intersection ( [source required] ).
Your struct is immutable, which is proper design. Whether or not to make a class instead, depends on how you would like to use this. I tend to prefer class more than struct. Note that a struct has an inherent default value, while a reference type is null by default. How would you distinguish a default value from a value that happens to have the same value? This is one reason to go with a class, unless you cope with this issue by using a Nullable&lt;T&gt; and accepting the overhead it provides.
It is good practice to override GetHashCode() and Equals().




Suggestions

1201ProgramAlarm's answer suggests to use IComparable on DimensionBound, but I would use it on the generic type parameter.

public class DimensionBound&lt;T&gt; where T : IComparable&lt;T&gt;
{
    public readonly T Value;
    public readonly bool IsInclusive;

    public DimensionBound(T value, bool isInclusive)
    {
        Value = value;
        IsInclusive = isInclusive;
    }
}


The interval could then be refactored to compare the values. Note that I would provide a convenience constructor accepting an inclusive start and exclusive end (which is considered common practice when dealing with continuous data). 

I would also favour instance methods over the static comparison. Furthermore, I would argue the purpose of the dimension in this class. I would remove it and call the class Interval. You can always create an additional class or use a tuple (int, Interval) to include the dimension.

public sealed class Interval&lt;T&gt; where T : IComparable&lt;T&gt;
{
    public readonly DimensionBound&lt;T&gt; Start;
    public readonly DimensionBound&lt;T&gt; End;

    public Interval(T start, T end)
        : this(ew DimensionBound&lt;T&gt;(start, true), new DimensionBound&lt;T&gt;(end, false))
    {
    }

    public Interval(DimensionBound&lt;T&gt; start, DimensionBound&lt;T&gt; end)
    {
        Start = start;
        End = end;
    }

    public bool Contains(T value)
    {
        // method body ..
    }

    public bool Intersects(Interval&lt;T&gt; other)
    {
        // method body ..
    }
}


If we don't change your specification and require both edges to be inclusive when 2 intervals meet, we could implement Intersects (Contains is similar) as follows (comments added for code review purposes only):

public bool Intersects(Interval&lt;T&gt; other)
{
    // compare start to other end
    var compareToStart = Start.Value.CompareTo(other.End.Value);

    // no intersection possible; instance starts after other is finished
    if (compareToStart == 1) return false;

    // both intervals meet but at least one of the bounds is exclusive, so no intersection
    if (compareToStart == 0 &amp;&amp; !(Start.IsInclusive &amp;&amp; other.End.IsInclusive)) return false;

    // compare end to other start
    var compareToEnd = End.Value.CompareTo(other.Start.Value);

    // no intersection possible; instance finishes before other is started
    if (compareToEnd == -1) return false;

    // both intervals meet but at least one of the bounds is exclusive, so no intersection
    if (compareToEnd == 0 &amp;&amp; !(End.IsInclusive &amp;&amp; other.Start.IsInclusive)) return false;

    return true;
}


This works with float, or any other IComparable&lt;T&gt;.

Assert.IsTrue(new Interval&lt;float&gt;(0, 1).Intersects(new Interval&lt;float&gt;(0.5f, 1.5f));