views:

205

answers:

4

Is there a succinct way to represent a bounded numeric value in .NET 3.5?

By this I mean a value such as a percentage (0 - 100) probability (0 - 1) or stock level (0 or above).

I would want a ArgumentOutOfRangeException (or equivalent) to be thrown if an out-of-range assignment was attempted. I would also want static MaxValue and MinValue properties to be available.

It was suggested in a comment to another SO question that this was a good use of a struct.

+1  A: 

This is, in fact, the type of thing used as an example of 'places to use a struct' in the MS Press study guide for the 70-526 exam. A small group of data (< 16 bytes), with value semantics, is best designed as a struct, not a class.

Harper Shelby
+3  A: 

I don't think there is a built-in way like the Ada "range" keyword, but you could easily create a type like RestrictedRange<T> that had a min and max value along with the current value:

public class RestrictedRange<T> where T : IComparable
{        
    private T _Value;

    public T MinValue { get; private set; }
    public T MaxValue { get; private set; }

    public RestrictedRange(T minValue, T maxValue)
        : this(minValue, maxValue, minValue)
    {
    }

    public RestrictedRange(T minValue, T maxValue, T value)
    {
        if (minValue.CompareTo(maxValue) > 0)
        {
            throw new ArgumentOutOfRangeException("minValue");
        }

        this.MinValue = minValue;
        this.MaxValue = maxValue;
        this.Value = value;
    }

    public T Value
    {
        get
        {
            return _Value;
        }

        set
        {
            if ((0 < MinValue.CompareTo(value)) || (MaxValue.CompareTo(value) < 0))
            {
                throw new ArgumentOutOfRangeException("value");
            }
            _Value = value;
        }
    }

    public static implicit operator T(RestrictedRange<T> value)
    {
        return value.Value;
    }        

    public override string ToString()
    {
        return MinValue + " <= " + Value + " <= " + MaxValue;
    }
}

Since there is an implicit conversion to automatically get the value, this will work:

var adultAge = new RestrictedRange<int>(18, 130, 21);
adultAge.Value++;
int currentAge = adultAge; // = 22

In addition, you can do things like this

var stockLevel = new RestrictedRange<int>(0, 1000)
var percentage = new RestrictedRange<double>(0.0, 1.0);
Jeff Moser
+1  A: 

I would probably create a class to represent the bounded value.

public class Percentage
{
    public static readonly double MinValue = 0.0;
    public static readonly double MaxValue = 100.0;

    private double value;
    public double Value
    {
       get { return this.value; }
       set
       {
            if (value < MinValue || value > MaxValue)
            {
                throw new ArgumentOutOfRangeException("...");
            }
            this.value = value;
       }
}

If your data is stored in a database, however, you can usually avoid this and simply use the natural underlying type for the value and enforce constraints on the database and in your data validation methods. Min/Max values can be stored in a configuration if necessary.

tvanfosson
+1, just beat me to it...
John Rasch
This can be made more generic taking advantage of IComparable, see my "RestrictedRange" implementation in my answer
Jeff Moser
To avoid surprises from reference semantics of classes, this may be better as a struct.
Richard
+1  A: 

You can, of course, enforce your bounds on any integer property in a class by checking them in the setter. However, this fails to clearly communicate the bounds requirements to users of that class. I tackled a similar problem in this question:
http://stackoverflow.com/questions/179295/how-do-i-indicate-a-validation-requirement-to-users-of-my-class

Based on that question and experience gained since, I propose you create a new BoundedInteger class. This class would have a value property, which should be self explanatory. It would also have implicit conversions to and from integer, to make it easy on users of your class. You would also add your MinValue and MaxValue items as readonly properties and and include them as required parameters in your class constructor.

One important addition is that you may not always want to throw an exception on bad assignments, because sometimes a bad assignment may not be all that exceptional. Depending on the context, there are several behaviors you can choose from when a bad assignment is attempted:

  • Allow the assignment anyway (for example: someone might really want a value of 110%)
  • Keep previous value
  • Reset to zero or some other "default" value
  • Set the value to the nearest bound (ie, if they try to assign 110 to a percentage, set it to 100 instead)

Any of those behaviors could also throw an exception or not, based on the needs of the property. Depending on your problem domain this may all be overkill, but it is worth keeping in mind as you design your program.

If you decide you do want this flexibility, you would want to define these behaviors in an enum, along with a read-only property to hold the value. Then, pick one for the default and then add an additional constructor where you can set a specific behavior for that instance along with whether an exception should be thrown. Finally, if you are going to potentially allow bad assignments you would want to provide an IsValid() method that tests against the bounds properties.

Joel Coehoorn