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

发布于 2024-08-17 12:57:44 字数 1105 浏览 6 评论 0 原文

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

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

    }
}

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

酒浓于脸红 2024-08-24 12:57:44

是的,您可以使用泛型来做到这一点 - 尽管不能使用 <>。您应该利用这些类型为自己实现 IComparable 的事实:(

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

您仍然可以在这里使用原始代码 - 我更喜欢这种条件条件的使用不过,它满足了我不断增长的功能倾向。)

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

我的影子我的梦 2024-08-24 12:57:44

(我迟到了,但想尝试一下)

我认为这个语法读起来很好:

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

(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;
        }
    }
}
小兔几 2024-08-24 12:57:44

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

它允许您使用常见的比较和一元运算符,例如 < <=> >= + -,并混合使用 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

    }
}

你可以将这种方法与 Bryan 流畅的界面结合起来,并取得很大的进步(尽管你可能并不真正需要这样做,而且这都是疯狂的矫枉过正)。

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

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)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文