Zine.net online

Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc
w Szukaj

arkadiusz.wasniewski

Range<T>

Porównań i walidacji w kodzie zawsze dużo jest. Czasem, jak u mnie ostatnio, warto spreparować sobie specjalną klasę operacje tego typu ułatwiającą. Koncept nie jest nowy. Poczytać można o nim między innymi na stronie Martina Fowlera. Ciekawa natomiast jest implementacja, którą można wykonać korzystając z platformy .NET.

Pierwsza wersja klasy w najważniejszych swoich częściach wyglądała następująco:

    class Range<T>

    {

        private readonly T minimum;

        private readonly T maximum;

        private readonly bool rangeIsExclusive;

 

        public bool Included(T value)

        {

            Debug.Assert(value != null);

 

            if(minimum > value){

                return false;

            }

            if(minimum == value && rangeIsExclusive){

                return false;

            }

            if (maximum < value) {

                return false;

            }

            if (maximum == value && rangeIsExclusive) {

                return false;

            }

 

            return true;

        }

    }

Nie było to najszczęśliwsze rozwiązanie. Skąd kompilator ma wiedzieć jak są dla potencjalnego typu zakresu zdefiniowane operacje porównania? Dla liczb całkowitych czy rzeczywistych jest to oczywiste. Ale dla obiektów zawierających np. imię i nazwisko pracownika już nie.

Jak zatem podejść do tematu? Rozwiązania należy szukać wśród interfejsów. Nakładamy na typ zakresu naszej klasy więzy, iż musi on implementować interfejs IComparable<T> umożliwiający porównywanie klas i struktur. Pełny kod źródłowy poniżej.

    /// <summary>

    /// Klasa opisująca dowolny zakres.

    /// </summary>

    /// <typeparam name="T">Typ wybranego zakresu.</typeparam>

    internal sealed class Range<T> where T : IComparable<T>

    {

        private readonly T minimum;

        private readonly T maximum;

        private readonly bool rangeIsExclusive;

 

        /// <summary>

        /// Konstruktor.

        /// </summary>

        /// <param name="minimum">Dozwolona wartość minimalna.</param>

        /// <param name="maximum">Dozwolona wartość maksymalna.</param>

        /// <remarks>

        /// Domyślnie badana wartość może być równa wartości minimalnej

        /// lub maksymalnej.

        /// </remarks>

        public Range(T minimum, T maximum)

            : this(minimum, maximum, false)

        {

        }

 

        /// <summary>

        /// Konstruktor.

        /// </summary>

        /// <param name="minimum">Dozwolona wartość minimalna.</param>

        /// <param name="maximum">Dozwolona wartość maksymalna.</param>

        /// <param name="rangeIsExclusive">Czy badana wartość musi być

        /// różna od wartości minimalnej i maksymalnej (nierówność

        /// ostra).</param>

        public Range(T minimum, T maximum, bool rangeIsExclusive)

        {

            Debug.Assert(minimum != null);

            Debug.Assert(maximum != null);

            Debug.Assert(minimum.CompareTo(maximum) <= 0);

 

            this.minimum = minimum;

            this.maximum = maximum;

            this.rangeIsExclusive = rangeIsExclusive;

        }

 

        /// <summary>

        /// Dozwolona wartość minimalna.

        /// </summary>

        public T Mimimum

        {

            get { return minimum; }

        }

 

        /// <summary>

        /// Dozwolona wartość maksymalna.

        /// </summary>

        public T Maximum

        {

            get { return maximum; }

        }

 

        /// <summary>

        /// Czy podawana parametrem wartość mieści się w zadanym zakresie.

        /// </summary>

        /// <param name="value">Wartość, którą chcemy sprawdzić.</param>

        /// <returns>Metoda zwraca <b>true</b> jeśli podana wartość mieści

        /// się w zakresie.</returns>

        public bool Included(T value)

        {

            Debug.Assert(value != null);

 

            int result;

 

            result = minimum.CompareTo(value);

            if((result > 0) || (result == 0 && rangeIsExclusive)) {

                return false;

            }

 

            result = maximum.CompareTo(value);

            if((result < 0) || (result == 0 && rangeIsExclusive)) {

                return false;

            }

 

            return true;

        }

    }

Przykładowe wykorzystanie:

            Range<double> discountRange = new Range<double>(0, 99.99);

            return discountRange.Included(23);

I to wszystko. Od teraz życia powinno stać się łatwiejsze. Przynajmniej w temacie porównywania zakresów.

Mała aktualizacja kilka godzin po publikacji posta:

  • Wojtek Gębczyk zwrócił mi uwagę, iż nazwa f-cji oznacza prędzej dodanie do zbioru niż sprawdzenie bycia w danym zakresie. I miał rację. Dlatego też zmieniłem nazwę metody Include na Included. Dzięki Wojtek;
  • Michał Grzegorzewski zaproponował sprawdzanie czy podana wartość maksymalna jest większa równa od wartości minimalnej. Dodałem Assert. Dzięki Michał;
  • I mała dyskusja jak nazwać inaczej metodę Included.... Może Contains? Ktoś ma jakieś pomysły?
Opublikowane 11 lipca 2008 15:30 przez arkadiusz.wasniewski

Komentarze:

 

mgrzeg said:

Heh, a mi przyszlo do lebka cos takiego:

public static class Extensions {
public static bool IsInRangeEx(this IComparable a, IComparable min, IComparable max) {
return (min.CompareTo(a) < 0 && a.CompareTo(max) < 0) ? true : false;
 }
}

i potem uzycie tego:

bool test = 50.IsInRangeEx(0, 100);
bool test = 50.1.IsInRangeEx(0.0, 100.0);

Oczywiscie do tego wariacje Ex, Inc, dodatkowy param - wedle uznania.

:)

lipca 11, 2008 19:00
 

rod said:

Oj to do konca nie wyjdzie bo mozesz wowczas dac rozne klasy.

Lepiej zrobic taka sygnature metody.

public static bool IsInRange<T>(this T obj, T min, T, max)

 where T : IComparable<T>

{

...

}

lipca 11, 2008 19:31
 

arkadiusz.wasniewski said:

Rozszerzenia klas, jako nowość .NET 3.x mogą ładnie upiększyć kod. Fluent Interface normalniuśko.

lipca 11, 2008 20:33
 

mgrzeg said:

@Daniel: wersja genericsowa jest przegadana :) I owszem - kompilator nie wzburzy sie jak zarzuci sie obiekty dwoch roznych typow, a wyjatek dostaniemy dopiero w runtimie :) (no, chyba, ze ktos dorzuci CompareTo w wersji na rozne typy, to wtedy wersja niegenericsowa jest ogolniejsza :)). Taki duck typing w wersji dla miesozernych ;)

lipca 11, 2008 22:57
 

arkadiusz.wasniewski said:

Zainstalowałem sobie przed chwilą Enterprise Library w wersji 4. Z ciekawości zajrzałem w kody źródłowe Validation Block. I coż :-) Wygląda, że ponownie odkryłem koło... ot może bardziej proste i szybsze.

lipca 18, 2008 23:34
Komentarze anonimowe wyłączone
W oparciu o Community Server (Personal Edition), Telligent Systems