Help is needed to avoid code duplication between multiple method signatures

I need to limit certain numbers to valid ranges in my application. I created delegates to handle this. I do not know whether to do it right; I am facing something that seems 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 the one hand, it seems that if I added more signatures, then I would like to be able to do different things with them (say, conversions or rounding, etc.). On the other hand, the code is exactly the same between the two signatures. Is this normal or is there a way to write a single operation that applies to both of these cases, even if other cases can use different operations?

+4
source share
3 answers

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 can still use your source code here - I like to use this conditional statement, but it satisfies my growing functional trends.)

+20
source

(I'm late to the party, but wanted to take a picture)

I think this syntax reads well:

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

You can do this by specifying a restriction interface:

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

Then define a static class that provides the implementation and a method that indicates 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; } } } 
+1
source

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

It allows you to use regular comparisons and unary operators such as <<= → = + - and mix usage between type T and type RestrictedNumber, so for example, you can pass RestrictedNumber to any method that expects a double, all the while keeping the initial value possible, out of range.

You never need to call any methods to perform restrictions or casting - everything can be set after the declaration.

See the second class below for usage examples and notes.

Inappropriate Oksama 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 Brian's fluent interface and do it pretty far (although you probably don't need to, and that's all a crazy overflow).

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)

+1
source

All Articles