Нужна помощь во избежании дублирования кода между несколькими сигнатурами методов - PullRequest
4 голосов
/ 15 января 2010

Мне необходимо ограничить определенные числа допустимыми диапазонами в моем приложении. Я создал делегатов, чтобы справиться с этим. Я не знаю, является ли это правильным способом сделать это; Я сталкиваюсь с чем-то, что кажется неправильным.

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 ]

21 голосов
/ 15 января 2010

Да, вы можете сделать это с помощью дженериков, но не с < и >. Вы должны использовать тот факт, что эти типы реализуют 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 голос
/ 15 января 2010

В зависимости от того, как вы будете использовать эти числа, могут быть случаи, когда будет полезен тип с неявным оператором.

Это позволяет вам использовать обычные сравнения и унарные операторы, такие как <<=>> = + -, и смешивать использование между типом 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

    }
}

Вы можете объединить этот подход с интерфейсом беглого понимания Брайана и сделать это довольно далеко (хотя вам, вероятно, это и не нужно, и это все сумасшедший перебор).

var n = Restrict<code><int> ._ (25) .to_be.greater_than (50);
var p = Restrict <double> ._ (1234,567) .to_be.greater_than (0d) .and.less_than (50000d)

1 голос
/ 15 января 2010

(я опаздываю на вечеринку, но хотел сделать снимок)

Я думаю, что этот синтаксис хорошо читается:

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;
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...