需要帮助避免跨多个方法签名的代码重复

时间:2010-01-14 21:35:30

标签: c# delegates code-duplication

我需要在我的应用程序中将某些数字限制为有效范围。我创建了代表来处理这个问题。我不知道这是否是正确的做法;我遇到了一些错误的事情。

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);

    }
}

一方面,似乎我添加了更多签名,然后我希望能够用它们做不同的事情(比如转换或舍入等)。另一方面,在这两个签名之间,代码完全相同。这是否可以,或者是否有某种方法可以编写适用于这两种情况的操作,即使其他情况可能使用不同的操作?

3 个答案:

答案 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)