views:

81

answers:

5

I'd like to restrict the value of a number parameter in a constructor to within a certain range.

I know the conventional way is to do something like the following:

public class Foo
{
    public int MaxAmount { get; }
    public int Amount { get; set; }

    public Foo(int amount)
    {
        if (amount > MaxAmount) { Amount = MaxAmount; }
        else if (amount < 1) { Amount = 1; }
        else { Amount = amount; }
    }
}

But what I don't like about this is that the caller doesn't know when the property gets set to something other than what was specified. I could return an exception instead of silently clamping the value, but that's not very friendly.

What I'd like is something akin to this:

public Foo(int(1, this.MaxAmount) amount) // Where int(minimumValue, maximumValue)
{
   Amount = amount;
}

in which one wouldn't even be able to instantiate Foo with an unacceptable value - the framework would prevent it.

Is anything like this possible?

EDIT FOR CLARITY:

What I'm after is a means by which the parameter itself can carry and communicate the information about its constraints - in a 'baked in' fashion which might, for example, surface in Intellisense when you wrote the call. So, I'd avoid the work of even attempting to instantiate the class if the values for the parameters were not valid.

If, for example, the program is running and the user types a number (N) and presses a button which creates a new Foo with an illegal quantity of N, I now have an exception to handle and something to debug and fix. Why even allow it in the first place? If Foo has been explicitly defined as having an upper boundary of 4 for its Amount property, what's the point of allowing the developer to write Foo(5) when I could have informed him that the value he's passing is not valid at the time that he wrote it?

If there's some syntactic sugar, like ParameterConstraint or something, that is handled by the Framework for me so that I don't have to roll my own into every class I write, I think that would be very useful.

+5  A: 

You can do this using static-contract checking with Code Contracts (Premium only - The standard edition only offers runtime contract checking).

The syntax is simply

public Foo(int amount) {
    Contract.Requires(amount < MaxAmount);
    ...
 }

(Requires) contracts are evaluated by checking that the arguments is constrained when calling the method. In your instance, it will be difficult to evaluate the constructor argument with against the instance field MaxAmount, because you cannot check that value beforehand. (Make MaxValue static to solve this).

Example of such call.

int val = _getFromSomewhere();
var foo = new Foo(val); 
//This May produce compile time error 
// because the contract checker cannot prove you contract is met.

The fix would be to make sure you put the constraint where you call is made.

int val = _getFromSomewhere();
if (val < Foo.MaxAmount)
    var foo = new Foo(val); 
    //Will always compile fine, because contract is met.

When you install Contracts, the static checker isn't turned on by default. Your project properties will have an extra tab where you can configure the contract options and enable static checking.

Mark H
Note that using `Contract.Requires` is not enough. You still need an `if (...) throw new ArgumentOutOfRangeException()`.
280Z28
+5  A: 

I could return an exception instead of silently clamping the value, but that's not very friendly.

Say what? What do you mean, "friendly"? The caller isn't your friend, it's another piece of code that is trying to set an out of range value. The developer who wrote the code should be told immediately that he's doing something wrong.

Throw an exception!

John Kugelman
I want to *inform* the caller that the value is invalid, but without doing the work of trying to instantiate the class. I think it would be nice to have Intellisense response that it's not valid, for one.
Superstringcheese
A: 

Either throw the exception or provide a static property to validate the amount

public static bool ValidateAmount(int amount)
{
    if(amount > MaxAmount)
        return false;
    return true;
}
Marco
+1  A: 

You will need to wrap the parameter up in a new type then. ints know what their max amount is, and it is int.MaxValue. If it is truly the case that the parameter itself knows its own max amount, and that it isn't something specific to class Foo, then you will need to create another type that checks the amount passed in to it. As it stands, the signature of Foo's constructor accepts any int data structure.

Sam Pearson
+1  A: 

Not for sure if this works for properties types in C#, but you might be able to define an enumeration contain all of the acceptable values and then set the data type of the property to that enumeration. That would force the caller to use the enumeration and thus know what values are acceptable. Of course, if you have a lot of values in the acceptable range, the enumeration would be unwieldy.

poke