views:

172

answers:

4

I have a need to restrict certain numbers to valid ranges in my application. I created delegates to handle this. I don't know if this is the right way to do it; I'm running into something that feels wrong.

public delegate int RestrictInteger(int minimum, int maximum, int value);
public delegate decimal RestrictDecimal(decimal minumum, decimal maximum, decimal value);

class GameMath
{
    public static int RestrictNumber(int minimum, int maximum, int value)
    {
        if (value < minimum) { value = minimum; }
        else if (value > maximum) { value = maximum; }
        return value;
    }
    public static decimal RestrictNumber(decimal minimum, decimal maximum, decimal value)
    {
        if (value < minimum) { value = minimum; }
        else if (value > maximum) { value = maximum; }
        return value;
    }
}
public class SomeClass
{
    public int aValue { get; set; }

    public void SetValue(int value)
    {
        RestrictInteger doRestrict = new RestrictInteger(GameMath.RestrictNumber);
        this.aValue = doRestrict(0, 100, value);

    }
}

On one hand, it seems like if I added more signatures, then I'd want to be able to do different things with them (say, conversions, or rounding, etc.). On the other hand, between these two signatures, the code is precisely the same. Is that okay, or is there some way to write one operation which applies to both these cases, even if other cases might use different operations?

+19  A: 

Yes, you can do this with generics - although not with < and >. You should use the fact that these types implement IComparable<T> for themselves:

public static T RestrictNumber<T>(T min, T max, T value) where T : IComparable<T>
{
    return value.CompareTo(min) < 0 ? min
         : value.CompareTo(max) > 0 ? max
         : value;
}

(You could still use your original code here - I rather like this sort of use of the conditional operator though; it satisfies my growing functional tendencies.)

Jon Skeet
Thank you. The syntax between the brackets is a bit arcane to me, but I'll puzzle it out. I think I get that the general idea is to trust the IComparable to worry about value type equality, and then write some appropriately abstract algorithm to handle whatever it lets through.
Superstringcheese
+1  A: 

[ this answer is really really wrong it turns out, it's here in its rubbish glory for reference though before i delete it, oops ]

These answers using generics are great, unless performance is an important factor.

If you're hitting this lots and lots and lots you're doing alot of boxing/unboxing which is a performance drain plus it will put pressure on the managed heap, resulting in more garbage collections, which is not great for performance either.

In this case I think you'd be better off with the repeated code and take the maintainability hit unless someone can come up with something clever.

(before i get massively flamed i'm not knocking generics or the other answers posted here ;)

runrunraygun
My solution doesn't require boxing... where do you think it would?
Jon Skeet
No, generics do not induce boxing. That's one of the cool things about them, in fact. A cast to `IComparable` *would* cause the value to be boxed, but a generic function with an `IComparable` constraint does not. The constraint is just a check that's performed at compile time. At JIT time (seems redundant to say that), a new version of the generic function is compiled for each value type it's called with. This way there is no boxing.
P Daddy
I was always under the impression the using value type in generics required boxing and unboxing all over the shop. I guess I've been wrong all this time :OShould i delete this answer or leave it for reference?
runrunraygun
I would prefer you leave it, if only for my own reference; I'll likely return to this post in the future and this part of the discussion may prove valuable.
Superstringcheese
+1  A: 

(I'm late to the party, but wanted to take a shot at it)

I think this syntax reads well:

Restrict.Value(x).ToBetween(0, 100)

You can do it by defining a restriction interface:

public interface IRestrictable<T> where T : IComparable<T>
{
    T ToBetween(T minimum, T maximum);
}

Then, define a static class which provides the implementation and a method which infers the type:

public static class Restrict
{
    public static IRestrictable<T> Value<T>(T value) where T : IComparable<T>
    {
        return new Restricter<T>(value);
    }

    private sealed class Restricter<T> : IRestrictable<T> where T : IComparable<T>
    {
        private readonly T _value;

        internal Restricter(T value)
        {
            _value = value;
        }

        public T ToBetween(T minimum, T maximum)
        {
            // Yoink from Jon Skeet

            return _value.CompareTo(minimum) < 0
                ? minimum
                : _value.CompareTo(maximum) > 0 ? maximum : value;
        }
    }
}
Bryan Watts
+1  A: 

Depending on how you will use these numbers, there may be instances when a type with an implicit operator will be useful.

It allows you to use common comparison and unary operators, such as < <= > >= + -, and to mix usage between the T type and the RestrictedNumber type, so, for example, you can pass a RestrictedNumber to any method that expects a double, all the while still holding on to the initial value that may have been out of range.

You never have to call any methods to perform restriction or casting -- everything can be set upon declaration.

See the second class below for usage examples and notes.

Having misplaced Occam's Razor:

public class RestrictedNumber<T> : IEquatable<RestrictedNumber<T>>, IComparable<RestrictedNumber<T>>
    where T: IEquatable<T>,IComparable<T>
{
    T min;
    T max;
    readonly T value;

    public RestrictedNumber(T min, T max, T value)
    {
        this.min = min;
        this.max = max;
        this.value = value;
    }

    public T UnrestrictedValue
    {
        get{ return value; }
    }

    public static implicit operator T(RestrictedNumber<T> n)
    {
        return get_restricted_value(n);
    }

    public static implicit operator RestrictedNumber<T>(T value)
    {
        return new RestrictedNumber<T>(value, value, value);
    }

    static T get_restricted_value(RestrictedNumber<T> n)
    {
        // another yoink from Jon Skeet
        return n.value.CompareTo(n.min) < 0 ? n.min
            : n.value.CompareTo(n.max) > 0 ? n.max
                : n.value;
    }

    T restricted_value
    {
        get { return get_restricted_value(value); }
    }

    public T Min // optional to expose this
    {
        get { return this.min; }
        set { this.min = value; } // optional to provide a setter
    }

    public T Max // optional to expose this
    {
        get { return this.max; }
        set { this.max = value; } // optional to provide a setter
    }

    public bool Equals(RestrictedNumber<T> other)
    {
        return restricted_value.Equals(other);
    }

    public int CompareTo(RestrictedNumber<T> other)
    {
        return restricted_value.CompareTo(other);
    }

    public override string ToString()
    {
        return restricted_value.ToString();
    }

}

public class RestrictedNumberExercise
{
    public void ad_hoc_paces()
    {
        // declare with min, max, and value
        var i = new RestrictedNumber<int>(1, 10, 15);

        Debug.Assert(i == 10d);
        Debug.Assert(i.UnrestrictedValue == 15d);

        // declare implicitly
        // my implementation initially sets min and max equal to value
        RestrictedNumber<double> d = 15d;
        d.Min = 1;
        d.Max = 10;

        Debug.Assert(i == 10d); // compare with other, "true" doubles
        Debug.Assert(i.UnrestrictedValue == 15d); // still holds the original value

        RestrictedNumber<decimal> m = new RestrictedNumber<decimal>(55.5m,55.5m,55.499m);

        Debug.Assert(m == 55.5m);
        Debug.Assert(m > m.UnrestrictedValue); // test out some other operators
        Debug.Assert(m >= m.UnrestrictedValue); // we didn't have to define these
        Debug.Assert(m + 10 == 65.5m); // you even get unary operators

        RestrictedNumber<decimal> other = 50m;

        Debug.Assert(m > other); // compare two of these objects
        Debug.Assert(other <= m); // ...again without having to define the operators
        Debug.Assert(m - 5.5m == other); // unary works with other Ts
        Debug.Assert(m + other == 105.5m); // ...and with other RestrictedNumbers
        Debug.Assert(55.5m - m == 0);
        Debug.Assert(m - m == 0);

        // passing to method that expects the primitive type
        Func<float,float> square_float = f => f * f;
        RestrictedNumber<float> restricted_float = 3;
        Debug.Assert(square_float(restricted_float) == 9f);

        // this sort of implementation is not without pitfalls
        // there are other IEquatable<T> & IComaparable<T> types out there...
        var restricted_string = new RestrictedNumber<string>("Abigail", "Xander", "Yolanda");
        Debug.Assert(restricted_string == "Xander"); // this works
        //Debug.Assert(restricted_string >= "Thomas"); // many operators not supported here

        var pitfall = new RestrictedNumber<int>(1, 100, 200);
        Debug.Assert(pitfall == 100);

        pitfall = 200;
        // Debug.Assert(pitfall == 100);
        // FAIL -- using the implicit operator is effectively
        // a factory method that returns a NEW RestrictedNumber
        // with min and max initially equal to value (in my implementation)
        Debug.Assert(pitfall == 200);

        pitfall = 10;
        Debug.Assert(pitfall.Min == 10 && pitfall.Max == 10);
        pitfall++;
        Debug.Assert(pitfall == 11); // d'oh!
        Debug.Assert(pitfall.Min == 11 && pitfall.Max == 11); // "it goes up to eleven"

        // if you need to change the input value for an existing
        // RestrictedNumber, you could expose a SetValue method
        // and make value not readonly

    }
}

You can combine this approach with Bryan's fluent-ish interface and take this pretty far (though you probably don't really need to and this is all crazy overkill).

var n = Restrict<int>._(25).to_be.greater_than(50);
var p = Restrict<double>._(1234.567).to_be.greater_than(0d).and.less_than(50000d)

Jay
This is absolutely overkill for what I need, but +1 for the most in-depth answer I've ever gotten :)
Superstringcheese