Zine.net online

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

arkadiusz.wasniewski

  • Y2K atakuje po latach

    Niektórzy pewnie pamiętają mój wpis dotyczący plików DBF. Nagłówek pliku DBF zawiera datę, gdzie na rok, miesiąc i dzień przeznaczone jest po jednym bajcie.

        [StructLayout(LayoutKind.Sequential)]

        internal struct Dbf3Header

        {

            public const byte ReservedSize = 20;

     

            public byte Dbf;

            public byte Year;

            public byte Mounth;

            public byte Day;

            public int RecordCount;

            public ushort HeaderSize;

            public ushort RecordSize;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = ReservedSize)]

            public byte[] Reserved;

        }

    W przypadku miesiąca i dnia jest to zakres wystarczający. Niestety jeśli chodzi o rok już nie. W zeszłym stuleciu (rany, ależ to poważnie brzmi) ze względu na niedostatek zasobów systemowych (pamiętacie stwierdzenie Gatesa, iż 640 KB każdemu wystarczy?) ograniczano do minimum przestrzeń dla dat zazwyczaj pamiętając dla roku tylko dwie ostatnie cyfry.

    Zamykając plik DBF wypada uaktualnić datę i czas. Fragment kodu poniżej:

                        DateTime now = DateTime.Now;

                        header.Year = (byte)(now.Year % 100);

                        header.Mounth = (byte)now.Month;

                        header.Day = (byte)now.Day;

    Tak to wygląda teraz. Dzięki dzieleniu modulo % do zmiennej Year przypisane są jedności i dziesiątki z roku. Jednak jeszcze kilkanaście dni temu zamiast dzielenia modulo miałem w kodzie zwykłe dzielenie. Skutek? Dla roku 2008 zamiast wartości 08 wpisywana była liczba 20. Jednym słowem chwila nieuwagi i padłem ofiarą roku 2000. Kto by pomyślał.

  • Bloggers Underground

    Paweł Potasiński chwil temu kilka rzucił zajawkę w kwestii spotkania o jakże wymownej nazwie Bloggers Underground. Zdecydowanie również wybieram się tam. Patrząc na listę uczestników zapowiada się ciekawie ;-) Może z tego powstanie jakaś nowa świecka tradycja?

    Jeśli chodzi o moją pogadankę to zajrzymy być może delikatnie w trzewia IHttpModule i IHttpHandler.

  • SmtpClient i <mailSettings>

    Czasami małe rzeczy sprawiają dużo radości. Jedną z nich jest możliwość ustawienia klienta pocztowego SmptClient poprzez wpisy w pliku konfiguracyjnym aplikacji. Rozważmy przykład:

                string mailAddresses = ConfigurationManager.

                    AppSettings["mailAddresses"];

                if (string.IsNullOrEmpty(mailAddresses)){

                    return;

                }

     

                MailMessage message = new MailMessage();

                message.From = new MailAddress("wnioski@homski.pl", "Wnioski");

                foreach (string mailAddress in mailAddresses.Split(';')){

                    message.To.Add(new MailAddress(mailAddress));

                }

                message.SubjectEncoding = Encoding.UTF8;

                message.Subject = "Tytuł wiadomości";

                message.BodyEncoding = Encoding.UTF8;

                message.Body = "Treść wiadomości";

     

                SmtpClient client = new SmtpClient();

                client.Send(message);

    Z pliki config aplikacji pobieram informację o adresach, do których należy pocztę wysłać. Następnie buduję wiadomość i wysyłam. Nie konfiguruję w kodzie programu zupełnie danych serwera SMTP, który posłuży do wysłania poczty. Zamiast tego pozwalam klasie SmptClient na samodzielne pobranie tych informacji z konfiguracji:

              <?xml version="1.0" encoding="utf-8" ?>

              <configuration>

                <system.net>

                  <mailSettings>

                    <smtp deliveryMethod="Network">

                      <network host="smtp.homski.pl"

                              userName=""

                              password=""/>

                    </smtp>

                  </mailSettings

                </system.net>

     

                <appSettings>

                  <add key="mailAddresses"

                      value="arkadiusz.wasniewski@data.pl"/>

                </appSettings>

              </configuration>

    Szczegóły schematu network (oraz elementów nadrzędnych) można znaleźć na stronach msdn.

  • Wirtualizacja aplikacji testowych

    Maszyn wirtualnych używam od dawna. I to korzystając zarówno z VMware Server jak i z Virtual PC. A jednak po  przeczytaniu na WSS.pl artykułu Praca z programem Virtual PC 2007, którego autorami są Robert Stuczynski i Jacek Doktór zorientowałem się, iż zupełnie nie wykorzystywałem do tej pory potęgi dysków różnicowych!

    Z racji tworzonego oprogramowania niejednokrotnie wykonuję integrację oprogramowania mobilnego z zewnętrznymi systemami np. sprzedaży. Tego typu systemy są dostępne do testów często w wersjach z ograniczeniem czasowym. Zdarza się, iż interesujący mnie komponent działa tylko kilkanaście dni. Dłużej trwa oczekiwanie na odpowiedź klienta w sprawie oferty. Ile razy można przeinstalowywać środowisko programisty?

    Korzystając z funkcjonalności dysków różnicowych utworzyłem dysk bazowy zawierający system operacyjny, Visual Studio oraz kilka przydatnych narzędzi. Teraz pozostaje zainstalować na dysku różnicowym tylko system testowy. W przypadku upłynięcia daty ważności licencji konieczne będzie jedynie utworzenie nowego dysku różnicowego.

  • Implementacja Inversion of Control

    Jedną z cech dobrego oprogramowania są luźne powiązania pomiędzy klasami. Droga do tego celu ciężka i kręta. Bez dwóch zdań. Wśród technik i wzorców, które należy w tym celu stosować znajdują się fabryki (Factory) oraz lokalizatory usług (Service Locator), dzięki którym tworzeniem instancji obiektów czy implementacji zadanych interfejsów zajmują się wyspecjalizowane klasy. Z tworzeniem obiektów, w kontekście wymienionych powyżej praktyk, związane są następujące koncepcje:

    • Inversion of Control - instancje klas pobierane są z zewnętrznych zasobów;
    • Dependency Injection - tworzenie instancji zleca się zewnętrznemu obiektowi (kontenerowi) znającemu zależności pomiędzy właściwymi klasami.

    Programista .NET chcący skorzystać z darmowych produktów IoC i DI ma do wyboru wiele rozwiązań. Przykładem może być StructureMap, którego autorem jest Jeremy Miller (prowadzi nota bene bardzo ciekawy blog), czy też Unity firmy Microsoft. W przypadku platformy .NET Compact Framework sytuacja nie wygląda już tak radośnie. Miesiące temu, kiedy poszukiwałem bezpłatnego kontenera mogącego działać w ramach aplikacji mobilnych, jedynym projektem był Mobile ObjectBuilder z Mobile Client Software Factory, który podobnie jak w bibliotekach przeznaczonych na platformę .NET do oznaczania relacji pomiędzy klasami wykorzystywał atrybuty. Niestety rozwiązanie to nie satysfakcjonowało mnie. Między innymi z powodu wydajności.

    Z braku istniejących darmowych produktów przeznaczonych na platformę .NET Compact Framework postanowiłem zaimplementować własne rozwiązanie typu Inversion of Control. Miało ono spełniać następujące kryteria:

    • Być wydajne i łatwe w użyciu;
    • Używać jak najmniej refleksji;
    • Nie używać plików konfiguracyjnych.

    Punkt pierwszy jest jasny i oczywisty. Pozostałe dwa założenia są konsekwencją znacznie mniejszych możliwości urządzeń mobilnych w zakresie mocy obliczeniowych czy zasobów pamięciowych w porównaniu z komputerami typu desktop, na których uruchamiana jest pełna wersja .NET. Ponieważ rozwiązanie przeznaczone było dla .NET Compact Framework, w projekcie nie zostały zupełnie uwzględnione takie kwestie jak wielowątkowość czy możliwość działania w ramach środowiska ASP.NET.

    Przy tworzeniu kontenera założyłem, iż programista jest podmiotem działania a nie przedmiotem (wiem co mówię, bo jestem politologiem). Innymi słowy założyłem, iż jest inteligentny. Stąd duża elastyczność przy konfigurowaniu rejestrowanego typu i do minimum ograniczone szukanie absurdów konfiguracyjnych. Zresztą w tym przypadku według mnie takie podejście jest z korzyścią dla czytelności i przejrzystości kodu.

    Efektem prac była biblioteka IoC nazwana dla uproszczenia... IoC. W zasobach do tego wpisu można znaleźć kody źródłowe stworzonego rozwiązania. W dalszej części artykułu przedstawię możliwości biblioteki i opiszę w jaki sposób można z niej korzystać.

    Gwoli wyjaśnienia na koniec. Wcześniej w swoich projektach używałem rozwiązania opartego na wzorcu Service Locator.

    IoC

    Powstałe rozwiązanie IoC, w dalszej części nazywane również kontenerem, składa się z trzech klas, z których tylko dwie są bezpośrednio dostępne programiście, i typu wyliczeniowego:

    • ObjectLocator - główna klasa rozwiązania. Dostępna jako Singleton. Pozwala rejestrować typy i pobierać ich instancje;
    • ObjectProfile - klasa umożliwiająca skonfigurowanie rejestrowanego typu. Oparta o wzorzec Fluent Interface;
    • ObjectCreator - wewnętrzna klasa statyczna odpowiedzialna za tworzenie instancji zarejestrowanych typów;
    • LifetimeStyle - typ wyliczeniowy. Dozwolone wartości czasu życia obiektu.

    Implementacja Inversion of Control

    Implementacja Inversion of Control

    Rozpoczęcie pracy z kontenerem polega na zarejestrowaniu typu. W najprostszej formie będzie to zarejestrowanie klasy:

                ObjectLocator.Register<ContactRepository>();

    Oczywiście w tym przypadku klasa nie może być abstrakcyjna ponieważ nie można utworzyć instancji takiej klasy.

    Aby móc zarejestrować interfejs, klasę abstrakcyjną czy też skonfigurować typ musimy skorzystać z bardziej wyrafinowanych metod. Aby uniknąć tworzenia wielu wersji metody rejestrującej wybrano wariant, w którym w czasie rejestracji przez metodę Register zwracany jest skojarzony z typem obiekt ObjectProfile umożliwiający konfigurację. Dzięki wzorcowi Fluent Interface (metody konfigurujące zwracają referencję do obiektu, który je zawiera) nie jest konieczne ustalanie poszczególnych właściwości tego obiektu. Wszystko można wykonać w czasie jednego wywołania. Zobaczmy więc jak wygląda rejestracja interfejsu:

                ObjectLocator.Register<IContactRepository>().

                    WithType<ContactRepository>();

    Parametrem metody WithType jest klasa, która implementuje rejestrowany typ. Parametr ten nie powinien być oczywiście klasą abstrakcyjną. W wywołaniu metody Register możliwe jest również skorzystanie z typów generic:

                ObjectLocator.Register<IRepository<Contact>>().

                    WithType<ContactRepository>();

    Identycznie jest w przypadku rejestrowania klas abstrakcyjnych.

    Domyślny czas życia obiektu w kontenerze to LifetimeStyle.Singleton, czyli wszystkie wywołania w kodzie będzie obsługiwała tylko jedna instancja klasy. Można oczywiście to zmienić tak, aby dla każdego wywołania tworzona była nowa instancja klasy:

                ObjectLocator.Register<IContactRepository>().

                    WithType<ContactRepository>().

                    AsSingleCall();

    Aby pobrać z kontenera instancję musimy wywołać metodę GetInstance podając w wywołaniu zarejestrowany typ. Poniżej wywołanie dla przypadku, kiedy zarejestrowaliśmy wcześniej interfejs:

                var repository = ObjectLocator.GetInstance<IContactRepository>();

    Oczywiście konstruktor obiektu, którego instancję tworzymy może zawierać parametry. Dla takich przypadków korzystamy z przeciążonej metody GetInstance:

                var repository = ObjectLocator.GetInstance<IContactRepository>(

                    new object[] { new Database() });

    W powyższym przykładzie przy tworzeniu obiektu korzystamy z konstruktora, który zawiera jeden parametr. Jeśli typ Database zostałby wcześniej zarejestrowany w kontenerze, możemy do konstruktora przekazać typ pobrany z IoC:

                var repository = ObjectLocator.GetInstance<IContactRepository>(

                    new object[] { ObjectLocator.GetInstance<Database>() });

    Wszystkie pokazane do tej pory wywołania metody GetInstance skutkowały utworzeniem instancji z wykorzystaniem refleksji, co nie zawsze jest dobrym rozwiązaniem. Dodatkowo wewnętrzna, statyczna klasa ObjectCreator może nie poradzić sobie w sytuacji kiedy klasa, której instancję chcemy utworzyć, posiada kilka konstruktorów o takiej samej liczbie parametrów i niektóre wartości przekazywane w wywołaniu metody GetInstance mają wartość null. Rozwiązaniem jest taka konfiguracja rejestrowanego typu, aby tworzenie instancji odbywało się poza kontenerem. Najprostszy sposób widzimy poniżej:

                ObjectLocator.Register<IContactRepository>().

                    WithInstance(new ContactRepository());

    Interfejs IContactRepository rejestrowany jest od razu z gotową instancją obiektu. Oczywiście powyższa konfiguracja ma sens tylko dla czasu życia obiektu LifetimeStyle.Singleton ponieważ przekazany obiekt będzie używany do obsługi wszystkich żądań.

    Zamiast przekazywać do kontenera gotowy obiekt, można proces tworzenia opóźnić do momentu, kiedy dana instancja rzeczywiście będzie potrzebna. Do tego służy metoda CallWhenCreating klasy ObjectProfile która jako parametr przyjmuje metodę zwrotną typu:

                Func<ObjectProfile<TRegisteredAs>, object[], TRegisteredAs>

    Delegat Fun wprowadzony w .NET 3.5 i zawierający powyższe parametry odpowiada następującemu delegatowi z .NET 2.0:

                delegate TRegisteredAs ObjectCreatingCallback<TRegisteredAs>(

                    ObjectProfile<TRegisteredAs> profile, object[] parameters)

                    where TRegisteredAs : class;

    Jak widzimy metoda tworząca otrzymuje z kontenera obiekt zawierający ustawienia zarejestrowanego typu oraz parametry dla konstruktora. Użycie nie jest skomplikowane. Najpierw wywołanie wykorzystujące delegat anonimowy:

                ObjectLocator.Register<IContactRepository>().

                    WithType<ContactRepository>().

                    CallWhenCreating(

                    delegate(

                        ObjectProfile<IContactRepository> profile,

                        object[] parameters)

                        {

                            var database = (Database)parameters[0];

                            return new ContactRepository(database);

                        });

    I wyrażenie lambda, nowość w ramach platformy .NET 3.5:

                ObjectLocator.Register<IContactRepository>().

                    WithType<ContactRepository>().

                    CallWhenCreating((profile, parameters) =>

                        {

                            var database = (Database)parameters[0];

                            return new ContactRepository(database);

                        });

    Jeśli tworząc nowy obiekt nie potrzebujemy informacji dotyczących konfiguracji typu oraz nie przekazujemy do konstruktora wartości możemy w przypadku delegata pominąć parametry:

                ObjectLocator.Register<IContactRepository>().

                    WithType<ContactRepository>().

                    CallWhenCreating(

                    delegate

                        {

                            return new ContactRepository();

                        });

    Ważna również jest możliwość wywołania zdefiniowanej metody zwrotnej po utworzenia obiektu. Przykładowo wskazane może być, aby dla każdego formularza w programie wstawić w tytule okna nazwę aplikacji, zdefiniować identyczne rozmiary itp. W celu zapewnienia spełnienia tych wymagań kontener zawiera metodę CallWhenCreated klasy ObjectProfile zadeklarowaną z następującym parametrem:

                Action<ObjectProfile<TRegisteredAs>, TRegisteredAs>

    Podobnie jak w przypadku metody zwrotnej wywoływanej w celu utworzenia obiektu, tak i tutaj delegat Action możemy przedstawić, jeśli chcielibyśmy korzystać z wcześniejszych wersji platformy .NET (zobacz uwagi na końcu notki), w postaci własnego delegata:

                delegate void ObjectCreatedCallback<TRegisteredAs>(

                    ObjectProfile<TRegisteredAs> profile, TRegisteredAs instance)

                    where TRegisteredAs : class;

    Tradycyjnie przykład zastosowania. Tym razem użyjemy zdefiniowanej metody zwrotnej:

                ObjectLocator.Register<IContactRepository>().

                    WithType<ContactRepository>().

                    CallWhenCreated(RepositoryCreated);

    Na koniec procesu rejestracji możemy sobie zażyczyć, aby instancja danego typu została od razu utworzona. Tę właściwość możemy wykorzystywać w przypadku dużych klas, które na pewno będą używane w czasie działania aplikacji. Oczywiście ma to sens jedynie w przypadku tych klas, dla których tylko jedna instancja będzie obsługiwała wszystkie żądania w programie. Dobrym przykładem są tutaj formularze. Poniżej stosowny przykład:

                ObjectLocator.Register<IContactRepository>().

                    AsSingleton().

                    WithType<ContactRepository>().

                    AndBuildUp();

    Oczywiście na tym etapie również istnieje możliwość przekazania do konstruktora klasy wymaganych wartości:

                ObjectLocator.Register<IContactRepository>().

                    AsSingleton().

                    WithType<ContactRepository>().

                    AndBuildUp(new object[] { "test" });

    Metoda AndBuildUp w przeciwieństwie do pozostałych metod klasy ObjectProfile zwraca utworzoną instancję klasy a nie wskazanie na obiekt konfigurujący.

    W praktyce

    Najwłaściwsze miejsce konfiguracji kontenera to start programu. Osobiście korzystam z fabryki abstrakcyjnej, której konkretne implementacje decydują o sposobie działania programu np.:

        internal abstract class ApplicationInitializer

        {

            /// <summary>

            /// Rejestrowanie widoków.

            /// </summary>

            public abstract void RegisterViews();

     

            /// <summary>

            /// Rejestracja fabryk abstrakcyjnych.

            /// </summary>

            public abstract void RegisterFactories();

     

            /// <summary>

            /// Rejestracja usług - interfejsu pomiędzy modelem a prezentacją.

            /// </summary>

            public abstract void RegisterServices();

     

            /// <summary>

            /// Rejestracja repozytoriów - klas umożliwiających dostęp do danych.

            /// </summary>

            public abstract void RegisterRepositories();

        }

    Zastosowany podział, czyli nazewnictwo i przeznaczenie metod wytwórczych, wynika głównie ze wzorca Model-View-Presenter wykorzystywanego z lubością przeze mnie w aplikacjach. Poniżej kilka przykładowych rejestracji widoków, gdzie formularze są w programie traktowane jako interfejsy:

            public override void RegisterViews()

            {

                ObjectLocator.Register<IAuthenticateView>().

                    WithType<AuthenticateForm>().

                    AsSingleCall().

                    CallWhenCreated(ViewCreated);

                ObjectLocator.Register<IDataGridView>().

                    WithType<DataGridForm>().

                    CallWhenCreated(ViewCreated).

                    AndBuildUp();

                ObjectLocator.Register<IMessageView>().

                    WithType<MessageForm>().

                    CallWhenCreated(ViewCreated);

            }

    Metoda ViewCreated pozwala na ustawienie parametrów wspólnych dla wszystkich formularzy takich jak widoczności wybranych kontrolek czy rozmiar. A tak wygląda rejestracja fabryk:

            public override void RegisterFactories()

            {

                ObjectLocator.Register<BusinessEntityFactory>();

                ObjectLocator.Register<DataGridStyleBuilder>();

                ObjectLocator.Register<DocumentHtmlFormatterFactory>();

            }

    W powyższym kodzie wszystkie fabryki są jednocześnie klasami implementującymi. Domyślnie obiekt tworzony jest w czasie pierwszego wywołania i istnieje przez cały czas pracy aplikacji. Zauważmy, iż dzięki takiemu podejściu jedynym singletonem w naszym kodzie pozostanie kontener IoC!

    Usługi dostarczają do prezenterów model:

            public override void RegisterServices()

            {

                ObjectLocator.Register<IAuthenticateService>().

                    WithType<AuthenticateService>().

                    AsSingleCall();

                ObjectLocator.Register<ICustomerService>().

                    WithType<CustomerService>().

                    AsSingleCall();

                ObjectLocator.Register<ISalesService>().

                    WithType<SalesService>().

                    AsSingleCall();

            }

    W przeciwieństwie do fabryk instancje usług będą tworzone dla każdego wywołania.

    Na koniec fragment odpowiadający za klasy umożliwiające dostęp do danych. Rejestracja powiązana jest z definicją delegatów odpowiedzialnych za tworzenie obiektów.

            public override void RegisterRepositories()

            {

                ObjectLocator.Register<Database>().

                    AsSingleton().

                    CallWhenCreating(

                    delegate

                        {

                            return new Database(connectionString);

                        });

                ObjectLocator.Register<ITransaction>().

                    WithType<Transaction>().

                    AsSingleCall().

                    CallWhenCreating(

                    delegate

                        {

                            return new Transaction(

                                ObjectLocator.Get<Database>());

                        });

     

                ObjectLocator.Register<ICustomerRepository>().

                    WithType<CustomerRepository>().

                    AsSingleCall().

                    CallWhenCreating(

                    delegate

                        {

                            return new CustomerRepository(

                                new CustomerDAO(

                                    ObjectLocator.Get<Database>(),

                                    ObjectLocator.Get<BusinessEntityFactory>(),

                                    new PaymentMethodConverter()));

                        });

            }

    Zakończenie

    Opisane powyżej rozwiązanie wykorzystuję z powodzeniem od bardzo dawna. I sprawdza się znakomicie. Udostępnione kody są oparte na nowej licencji BSD. Nie ma więc problemu aby, jeśli ktoś chce, wykorzystać zaprezentowaną przeze mnie implementację IoC we własnych rozwiązaniach - również komercyjnych. Z chęcią wysłucham również wszelkich uwag dotyczących przedstawionego rozwiązania.

    Jeśli chodzi o wersję platformy .NET i .NET Compact Framework to rozwiązanie zostało oparte o wydanie 3.5. Osoby, które chciałyby wykorzystać opisany kontener we wcześniejszych wersjach muszą zastąpić wyrażenia lambda delegatami, oraz wywołania Action oraz Fun podanymi przeze mnie odpowiednikami własnych delegatów. Oczywiście z powodu wykorzystywanych typów generic kontener ten nie będzie działać na platformie .NET 1.1.

    Pliki do artykułu:

    • IoC - kody źródłowe.

     

    PS. Michał Grzegorzewski ogłosił jakiś czas temu konkurs na projekt Open Source z główną nagrodą w postaci MSDN w wersji Premium. Jeśli ktoś nie ma pomysłu na taki projekt to podpowiadam. Można spróbować rozbudować powyższe rozwiązanie o możliwość pracy w środowiskach wielowątkowych czy ASP.NET.

  • Refaktoryzacja metod zwrotnych

    Najnowsza refaktoryzacja kodu jednego z moich projektów polegała na usunięciu wszystkich własnych definicji delegatów będących metodami zwrotnymi. Zamiast tego użyłem standardowych metod z przestrzeni nazw System:

    dla metod zwrotnych, które nie zwracają wartości oraz:

    dla metod zwrotnych zwracających wartość.

    Dzięki temu zniknęło kilka klas. A sam kod wbrew pozorom stał się bardziej przejrzysty przez to, iż nie trzeba zastanawiać się jak wygląda metoda WorkflowTerminateCallback jeśli teraz w kodzie występuje wywołanie metody Action. Wbrew pozorom, ponieważ nazwa metody WorkflowTerminateCallback jest bardziej wymowna od metody Action i niepokoiło mnie, czy przypadkiem źródła nie staną się mniej przejrzyste. Kod żyje jednak w pewnym kontekście i po wprowadzeniu zmian okazało się, iż moje obawy były bezzasadne.

  • 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?
  • MarshalHelper zarządza niezarządzanym

    We notce dotyczącej iteratorów czy też kiedy opisywałem operacje na plikach DBF posługiwałem się klasą pomocniczą MarshalHelper, która wykonywała wszystkie niezbędne czynności przy konwersji typów z kodu zarządzanego do niezarządzanego i vice versa. Wykorzystanie klas przestrzeni System.Runtime.InteropServices było konieczne, ponieważ w kodzie zarządzanym nie mamy gwarancji, iż pola z danej struktury będą w pamięci ułożone w tej samej kolejności i wyrównane powiedzmy do 8 bajtów.

    Sama implementacja metod pomocniczych klasy nie jest skomplikowana. W sieci można znaleźć trochę pomysłów. Poniżej kod całej klasy:

        internal static class MarshalHelper

        {

            public static Encoding encoding = Encoding.GetEncoding(852);

     

            /// <summary>

            /// Zapisanie struktury w pamięci do tablicy bajtów.

            /// </summary>

            /// <param name="structure">Struktura lub klasa, którą chcemy

            /// zapisać.</param>

            /// <returns>Strumień bajtów zawierający strukturę danych.</returns>

            public static byte[] ToByteArray<T>(T structure)

            {

                byte[] buffer = new byte[Marshal.SizeOf(structure)];

                GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

                Marshal.StructureToPtr(structure, handle.AddrOfPinnedObject(), false);

                handle.Free();

                return buffer;

            }

     

            /// <summary>

            /// Zapianie łańcucha znaków do tablicy bajtów z uwzględnieniem

            /// kodowania (zmiany strony kodowej).

            /// </summary>

            /// <param name="field">Łańcuch, który będziemy zapisywać.</param>

            /// <param name="fieldLength">Liczba bajtów tablicy.</param>

            /// <returns>Tablica bajtów zawierająca łańcuch po operacji

            /// zmiany strony kodowej.</returns>

            public static byte[] StringToByteArray(string field, int fieldLength)

            {

                Debug.Assert(fieldLength > 0);

     

                if(field == null){

                    field = string.Empty;

                }

                byte[] source = encoding.GetBytes(field);

                byte[] destination = new byte[fieldLength];

                Debug.Assert(source.Length <= fieldLength);

                Array.Copy(source, destination, source.Length);

                return destination;

            }

     

            /// <summary>

            /// Zapisanie tablicy znaków jako łańcucha znaków z uwzględnieniem zmiany

            /// strony kodowej.

            /// </summary>

            /// <param name="field">Tablica znaków, którą będziemy zapisywać.</param>

            /// <returns>Łańcuch zawierający tablicę znaków z uwzględnieniem

            /// zmiany strony kodowej.</returns>

            public static string ByteArrayToString(byte[] field)

            {

                string s = encoding.GetString(field, 0, field.Length);

                int index = s.IndexOf('\0');

                return index > 0 ? s.Remove(index, s.Length - index) : s;

            }

     

            /// <summary>

            /// Odczytanie struktury ze strumienia.

            /// </summary>

            /// <typeparam name="T">Typ struktury.</typeparam>

            /// <param name="stream">Strumień, z którego czytamy.</param>

            /// <returns>Zwracana struktura.</returns>

            public static T FromStream<T>(Stream stream)

            {

                Debug.Assert(stream != null);

     

                byte[] buffer = new byte[Marshal.SizeOf(typeof(T))];

                int bytesRead = stream.Read(buffer, 0, buffer.Length);

                if(bytesRead == 0){

                    return default(T);

                }

                if(bytesRead != buffer.Length){

                    throw new EndOfStreamException(

                        string.Format(

                            "Rozmiar bufora: {0}. Liczba bajtów odczytanych: {1}.",

                            buffer.Length, bytesRead));

                }

                GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

                T structure = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(),

                                                        typeof(T));

                handle.Free();

                return structure;

            }

     

            /// <summary>

            /// Zapisanie struktury do strumienia.

            /// </summary>

            /// <typeparam name="T">Typ struktury.</typeparam>

            /// <param name="stream">Strumień, do którego zapisujemy.</param>

            /// <param name="structure">Zapisywana struktura</param>

            public static void ToStream<T>(Stream stream, T structure)

            {

                Debug.Assert(stream != null);

     

                byte[] buffer = ToByteArray(structure);

                stream.Write(buffer, 0, buffer.Length);

            }

        }

    Życzę miłej zabawy.

  • Debugger.Break ładuje pakiety

    W czasie testowania oprogramowania mobilnego zdarza się, iż zdalne debuggowie przestaje funkcjonować. Obok ustawionych punktów wstrzymania pojawia się zaś ikona, która po najechaniu kursorem wyświetla komunikat, iż zdalna praca jest niemożliwa, ponieważ dany pakiet nie został załadowany. Co ciekawe dalej może rozpocząć wykonywanie krokowe aplikacji. Restart Visual Studio w takiej sytuacji nie pomaga.

    Okazuje się, iż rozwiązanie problemu jest trywialne. Należy w kodzie programu, gdzie chcemy ustawić punkt wstrzymania wstawić następujące polecenie:

                Debugger.Break();

    Dzięki temu debugger zostanie przywrócony do żywych.

  • Iteratory

    Mój pierwszy blog znajdował się na portalu developers.pl. Niestety z różnych przyczyn serwis ten padł. A szkoda. Choćby dlatego, iż miałem tam kilka ciekawych wpisów. Nie chciałbym aby zostały one wszystkie stracone dlatego też postanowiłem jeden z nich przypomnieć (również sobie). Ciekawa była optymalizacja pierwotnego kodu w ramach komentarzy do tej notki...

    Jedną z nowości .NET w wersji 2.0 są (dzisiaj już możemy powiedzieć, że były) iteratory. Dzięki nim przekazywanie kolekcji obiektów czy struktur pomiędzy klasami nie musi już oznaczać zajmowania nowych, pomocniczych obszarów w pamięci, co konieczne było zwłaszcza jeśli nasze oprogramowanie zbudowane było w oparciu o warstwy (dostępu do danych, logiki biznesowej czy prezentacji).

    Załóżmy, iż tworzymy rozwiązanie, które będzie zapisywało różne obiekty do pliku. Strumień z danymi wyjściowymi będzie zawsze ten sam, ale dane wejściowe możemy odbierać różnymi kanałami: z bazy danych, innych plików itd. Jedna z zapisywanych klas wyglądać będzie następująco:

        [StructLayout(LayoutKind.Sequential, Pack = 4)]

        public class ProductCategory

        {

            public const byte CodeSize = 21;

            public const byte NameSize = 41;

     

            public int Id;

     

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = ProductCategory.CodeSize)]

            public byte[] Code;

     

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = ProductCategory.NameSize)]

            public byte[] Name;

        }

    Zamiast klas można również próbować wykorzystać struktury. Okazuje się jednak, iż nie jest to dobry sposób, zwłaszcza jeśli będziemy korzystać z pętli foreach. Powodem są operacje boxing i unboxing (czyli rzutowania w obie strony pomiędzy klasą Object a naszym typem), które wykonywane są w czasie przechodzenia po kolejnych elementach dla value type, do których poza typami prymitywnymi należą właśnie struktury. Może się okazać, iż straty na tych operacjach będą nie do zaakceptowania.

    Obiekt zapisujący dane do strumienia wyjściowego, czyli do pliku będzie wspólny dla wszystkich struktur i będzie zawierał między innymi metodę WriteEntities.

        internal sealed class BusinessEntityProcess

        {

     

            ...

     

            private void WriteEntities(string filePath, IEnumerable entities)

            {

                using (FileStream fs