【问题标题】:Need help avoiding code duplication across multiple method signatures需要帮助避免跨多个方法签名的代码重复
【发布时间】:2010-01-14 21:35:29
【问题描述】:

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

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

    }
}

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

【问题讨论】:

  • 看看泛型。

标签: c# delegates code-duplication


【解决方案1】:

是的,你可以使用泛型来做到这一点——尽管&lt;&gt; 不行。您应该利用这些类型为自己实现 IComparable&lt;T&gt; 的事实:

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

(您可以在这里仍然使用您的原始代码 - 不过我更喜欢这种使用条件运算符的方式;它满足了我不断增长的函数式倾向。)

【讨论】:

  • 谢谢。括号之间的语法对我来说有点神秘,但我会弄明白的。我想我明白一般的想法是相信 IComparable 担心值类型相等,然后编写一些适当的抽象算法来处理它允许通过的任何事情。
【解决方案2】:

(我参加聚会迟到了,但想试一试)

我认为这个语法读起来不错:

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

【讨论】:

    【解决方案3】:

    根据您将如何使用这些数字,在某些情况下,带有隐式运算符的类型可能会很有用。

    它允许您使用常见的比较和一元运算符,例如 >= + -,并在 T 类型和 RestrictedNumber 类型之间混合使用,例如,您可以将 RestrictedNumber 传递给任何期望双精度的方法,同时仍然保留可能超出范围的初始值。

    您永远不必调用任何方法来执行限制或强制转换——一切都可以在声明时设置。

    有关用法示例和说明,请参见下面的第二类。

    奥卡姆剃刀规则放错地方了:

    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
    
        }
    }
    

    您可以将这种方法与 Bryan 流利的界面结合起来,并走得更远(尽管您可能并不真正需要,这都是疯狂的矫枉过正)。

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

    【讨论】:

    • 这对于我需要的东西来说绝对是矫枉过正,但是 +1 是我得到的最深入的答案:)
    • 很久以后,我正在重新审视这个问题,寻找一种方法来完全按照您上面的描述进行操作 - 希望我能再次为您投票 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多