我需要在我的应用程序中将某些数字限制为有效范围。我创建了代表来处理这个问题。我不知道这是否是正确的做法;我遇到了一些错误的事情。
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);
}
}
一方面,似乎我添加了更多签名,然后我希望能够用它们做不同的事情(比如转换或舍入等)。另一方面,在这两个签名之间,代码完全相同。这是否可以,或者是否有某种方法可以编写适用于这两种情况的操作,即使其他情况可能使用不同的操作?
答案 0 :(得分:21)
是的,您可以使用泛型来执行此操作 - 但不能使用<
和>
。您应该使用这些类型为自己实现IComparable<T>
的事实:
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;
}
(你可以仍然在这里使用你的原始代码 - 我更喜欢这种条件运算符的使用;它满足了我不断增长的功能倾向。)
答案 1 :(得分:1)
(我迟到了,但是想拍一下)
我认为这种语法很好:
Restrict.Value(x).ToBetween(0, 100)
您可以通过定义限制界面来实现:
public interface IRestrictable<T> where T : IComparable<T>
{
T ToBetween(T minimum, T maximum);
}
然后,定义一个提供实现的静态类和一个推断类型的方法:
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;
}
}
}
答案 2 :(得分:1)
根据您将如何使用这些数字,可能会出现具有隐式运算符的类型有用的情况。
它允许您使用常见的比较和一元运算符,例如&lt; &lt; =&gt; &gt; = + - ,并混合T类型和RestrictedNumber类型之间的使用,因此,例如,您可以将RestrictedNumber传递给任何需要double的方法,同时仍然保持可能具有的初始值已超出范围。
您永远不必调用任何方法来执行限制或转换 - 一切都可以在声明时设置。
有关使用示例和注释,请参阅下面的第二节。
错误地放置了奥卡姆的剃刀:
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
}
}
你可以将这种方法与布莱恩的流畅界面结合起来,并将其放在相当远的地方(尽管你可能并不需要这样做,这都是疯狂的矫枉过正)。
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)