Posts tagged ‘C#’

Statyczne składowe i takież konstruktory

2008-06-04 23:30

Wiadomo, że konstruktor to specjalna metoda, która jest wywoływana przy tworzeniu nowego obiektu i ma za zadanie go zainicjalizować. Okazuje się, że w innej wersji “konstruktor” (albo coś, co go przypomina) może służyć do inicjalizacji nie obiektu, lecz samej klasy. A co takiego ma klasa, że należy czasem zadbać o jej prawidłową inicjalizację? Ano chociażby składowe statyczne – wspólne dla wszystkich obiektów.
W C# możemy sobie zdefiniować statyczny konstruktor, który może się tym zająć – jak również (niemal) czymkolwiek innym. Taki konstruktor, podobnie jak zwykły ma postać metody o tej samej co klasa nazwie, lecz jest opatrzony modyfikatorem static. Zostaje on wywołany przed stworzeniem pierwszego obiektu klasy lub próbą dostępu do jakiejkolwiek składowej statycznej. Całkiem praktyczne, prawda?
Coś podobnego ma również Java, a zwie się to oryginalnie ‘statycznym inicjalizatorem’. W odróżnieniu od wariantu z C#, inicjalizator ten jest wywoływany wcześniej – już w momencie załadowania klasy przez maszynę wirtualną. Właściwie jednak nie ma to wielkiego znaczenia, bo efekt jest analogiczny: jeśli ów inicjalizator ma zrobić coś przed pierwszym użyciem klasy, na pewno zostanie to zrobione. Jego kod pisze się w definicji klasy, w bloku otoczonym nawiasami klamrowymi i opatrzonym – a jakże – słówkiem static.

A jak pod tym względem wypada C++? No cóż, jak zwykle blado :) Kruczkiem standardu jest fakt, że wszelkie składowe statyczne klas mają niezdefiniowaną kolejność inicjalizacji, jeśli są przypisane do odrębnych modułów (plików .cpp). Jeśli więc takie składowe zależą od siebie, to wynikiem mogą być tylko kłopoty.
Poradzić na to można dwojako – zależnie od tego, którą zaletę statycznych konstruktorów chcemy wykorzystać. Jeżeli na przykład chcemy, aby jakaś zmienna statyczna lub globalna była zainicjowana niezależnie od tego, gdzie i kiedy chcemy ją użyć, najlepiej uczynić ją lokalną statyczną zmienną w funkcji:

  1. Foo& GetFoo() { static Foo foo; return foo; }

Jeśli z kolei musimy mieć pewność, że przed stworzeniem obiektu naszej klasy zostaną wykonane jakieś dodatkowe czynności, to możemy to częściowo osiągnąć za pomocą statycznej flagi:

  1. class Foo
  2. {
  3.     public:
  4.         Foo()
  5.         {
  6.             if (!ms_bInitialized)
  7.             {
  8.                   // (czynności konieczne przed użyciem klasy)
  9.                   ms_bInitialized = true;
  10.             }
  11.         }
  12. };
  13. Foo::ms_bInitialized = false;

Niezależnie jednak od tego, co jest nam akurat potrzebne, nie powinniśmy nigdy zapominać o rozważeniu zależności między składowymi statycznymi klas i/lub zmiennymi globalnymi w naszym kodzie, jeśli chcemy uniknąć niezdefiniowanego zachowania. Bo wtedy po prostu wszystko się zdarzyć może :]

Tags: , , , ,
Author: Xion, posted under Programming » 3 comments

Struktury danych nieobecne w .NET

2008-05-31 16:00

Programując z użyciem platformy .NET można niekiedy mieć wrażenie, że jest w niej niemal wszystko. Oczywiście takie przekonanie jest nietrwałe: wystarczy bowiem znaleźć coś, co być w niej mogłoby (a zwłaszcza coś, co występuje w innych, podobnych bibliotekach), a czego jednak nie ma. Nietrudno zauważyć, że takich rzeczy jest mimo wszystko większość – bo w sumie nie może być przecież inaczej :)
Niektóre braki są aczkolwiek bardziej dotkliwe niż inne. Należą do nich chociażby luki w systemie kolekcji obiektów, czyli – mówiąc ogólniej – wszelkiego rodzaju struktur danych. W .NET brakuje bowiem takich rzeczy jak:

Niektóre pojemniki w .NET 2.0
Niedużo tego…
  • Zbiory. Wprawdzie niemal każdy pojemnik zawiera metodę Contains o wiadomym przeznaczeniu, jednak tylko kontenery słownikowe sprawdzają unikalność umieszczanych w nich obiektów. Te zaś zasadniczo służą do czegoś innego: mapowania jednych obiektów na inne. Brakuje więc zwykłej, prostej klasy Set z operacjami dodawania, usuwania i wyszukiwania elementów – odpowiednika std::set z STL.
  • Kolejki priorytetowe (priority queues). Tutaj znowu można je symulować przy pomocy klas typu SortedDictionary, które jednak nie są zasadniczo do tego przeznaczone. Kolejka priorytetowa powinna mieć przecież niemal identyczny interfejs do zwykłej kolejki FIFO (czyli głównie metody Push i Pop), bo różnica polega wyłącznie na porządku zwracanych elementów, określonym przez ich porównania.
  • Kolekcje zbiorów rozłącznych (disjoint sets). Te dość specyficzne struktury danych są przydatne wtedy, gdy musimy szybko zorganizować kolekcję obiektów w osobne podgrupy wedle jakichś kryteriów. Można to oczywiście zrobić za pomocą dowolnych pojemników, tyle że będzie to nieefektywne. Dedykowana implementacja z użyciem wydajnych algorytmów (tzw. łączenia według rangi i kompresji ścieżek) byłaby znacznie bardziej na miejscu.
  • Grafy. Wprawdzie grafy nie są aż tak często wykorzystywane, ale nie da się ukryć, że czasami ich użycie jest konieczne. Na pewno nie zaszkodziłoby posiadanie ich jako wbudowanego feature‘a w każdej bibliotece struktur danych. Zwłaszcza z gotowym zestawem podstawowych algorytmów grafowych.

Tę listę można by oczywiście ciągnąć bardzo długo, ale zdecydowałem się ograniczyć do tych czterech rzeczy z dwóch powodów. Po pierwsze, przynajmniej raz zdarzyło się, że potrzebowałem którejś z wymienionych tutaj struktur w .NET i byłem zmuszony poradzić sobie we własnym zakresie. Po drugie, bez problemu można podać przykłady innych bibliotek, języków czy środowisk, gdzie jedna z nich, większość lub nawet wszystkie są obecne. Najlepszym przykładem jest C++ (z STL) uzupełniony o bibliotekę Boost, który pod względem struktur danych prezentuje się w tej postaci naprawdę potężnie.
Nie widzę więc specjalnych usprawiedliwień dla faktu, że w .NET lista standardowych struktur prezentuje się nadzwyczaj skromnie. Może więc zamiast dodawać w nowych wersjach platformy kolejne wątpliwej jakości “usprawnienia” – jak wyrażenia lambda wbudowane w C# czy zabawki typu LINQ – programiści Microsoftu zajęliby się raczej zaniedbanymi dotąd podstawami?…

Tags: , , ,
Author: Xion, posted under Programming » 3 comments

O układaniu kontrolek

2008-05-19 20:57

Najprostszym sposobem na rozmieszczanie kontrolek w oknie programu jest oczywiście… podanie ich pozycji i rozmiarów dosłownie. Ta metoda sprawdza się dobrze jednak tylko w prostych przypadkach: gdy mamy stałą (i raczej niewielką) liczbę obiektów, gdy rozmiary okna są stałe, gdy nie dostosowujemy rozmiaru interfejsu do rozdzielczości ekranu, itd. W wielu przypadkach to jednak nie wystarcza i dlatego systemy GUI oferują też inne narzędzia pozycjonowania elementów interfejsu. Niektóre z nich są całkiem pomysłowe. Oto kilka przykładów:

  • Przykład dokowania kontrolek
    Przykład dokowania

    Dokowanie (docking), zwane też wyrównywaniem (align). Polega to na “przyklejeniu” kontrolki do którejś z krawędzi nadrzędnego pojemnika (np. okna) w jednym wymiarze oraz do obu krawędzi w wymiarze prostopadłym. Komponentowi pozostaje wówczas jeden stopień swobody. Przykładowo, kontrolka zadokowana po lewej stronie może się jedynie rozszerzać w prawo (lub zmniejszać w lewo), zaś jej górna i dolna krawędź pokrywają się zawsze z odpowiednimi krawędziami zawierającego ją pojemnika.
    Ten sposób pozycjonowania jest idealny chociażby dla pasków stanu (status bar), które zawsze umiejscowione są w dolnej krawędzi okna.

  • Ustawianie zakotwiczania
    Ustawianie zakotwiczania
    w Visual Studio

    Zakotwiczanie (anchoring) polega z kolei na ustawieniu stałej odległości pewnych krawędzi kontrolki od odpowiednich krawędzi kontrolki nadrzędnej. Domyślnie na przykład każdy komponent jest zakotwiczony z lewej i z góry. Gdy zmieniamy rozmiar zawierającego go okna, dystans między lewą krawędzią tegoż okna i lewą krawędzią kontrolki pozostaje stały; podobnie jest z górnymi brzegami. Zmieniając ustawienia zakotwiczania możemy uzyskać efekt kontrolki “wyrównanej” w podobny sposób jak tekst w edytorze, np. do prawej strony okna zamiast do lewej.
    Anchoring można uznać za nieco lepszą wersję dokowania, ale w istocie różni się on od niego dość znacznie (zwłaszcza jeśli dokujemy wiele kontrolek po jednej stronie). Jest on jednak przydatny wtedy, gdy kontrolka ma w określony sposób zmieniać rozmiar wraz z oknem.

  • Układ typu flow
    Układ typu flow

    Układy (layouts) kontrolek. Są to bardziej wyspecjalizowane narzędzia, których jedynym zadaniem jest układanie elementów interfejsu w określony sposób. Do najbardziej przydatnych należy układ typu flow oraz układ tabelkowy. Ten pierwszy polega na rozmieszczaniu kontrolek po kolei (w ustalonym porządku) tak, jak wypisuje się tekst w kolejnych wierszach. (Jak widać, analogie tekstowe w projektowaniu interfejsu pojawiają się całkiem często ;]). Z kolei układ tabelkowy, jak nazwa wskazuje, dzieli zadany obszar na wiersze i kolumny tak, że w każdej z powstałych w ten sposób komórek mieści się dokładnie jedna kontrolką i wypełnia ją całkowicie.
    Układy mają spore zastosowanie zwłaszcza wtedy, gdy rozmieszczane kontrolki są tworzone dynamicznie w trakcie działania programu. Potrafią wówczas zaoszczędzić dużo pracy z ręcznym przeliczaniem ich pozycji i/lub rozmiarów.

To stwierdzenie odnosi się zresztą do każdego z tych trzech sposobów na określanie wymiarów elementu interfejsu. Obecnie bardzo często okazuje się, że nawet stosunkowo zaawansowany układ kontrolek, który musi dostosowywać się do zmian rozmiaru okna, da się stworzyć bez obsługiwania zdarzeń typu OnResize i ręcznego rozmieszczania komponentów. A to może oszczędzić kilku kłopotów i pozwala łatwiej skoncentrować się na tych częściach programu, których wprawdzie nie widać, ale które są o wiele ważniejsze :)

Tags: , , ,
Author: Xion, posted under Programming » 1 comment

Przerywanie działania wątku

2008-05-12 21:17

Jednym z powodów używania wątków jest możliwość przerwania wykonywanych przezeń czynności właściwie w dowolnym momencie. Dzięki temu można na przykład wyposażyć aplikację okienkową w magiczny przycisk Anuluj obok paska postępu. Przez to zaś użytkownik ma wrażenie, że – nawet jeśli musi (dłuższą) chwilę zaczekać – nadal jest panem sytuacji :)
Jak można więc przerwać wątek, jeśli zachodzi taka potrzeba? Sposobów jest kilka:

  1. Najelegantszym jest sprawienie, aby zakończył się on “sam z siebie” i poczekanie na niego. Polega to na ustawieniu flagi sygnalizującej wątkowi konieczność zakończenia, którą oczywiście musi on regularnie sprawdzać. Stąd też główne pętle występujące w procedurach wątków są często w stylu while (!Terminating) { /* ... */ }. Dla porządku warto też (a w niektórych środowiskach trzeba), po ustawieniu rzeczonej flagi na wątek zaczekać. Czyni się to zwykle funkcją z join (‘złącz’) w nazwie: Thread.Join/join w .NET/Javie, pthread_join w POSIX-ie i… WaitForSingleObject w Windows API :)
  2. Jeśli wątek sporą część czasu poświęca na czekanie lub chociaż co jakiś czas przechodzi przynajmniej na krótko w stan uśpienia, wówczas można mu przerwać (interrupt). W .NET/Javie robi się to poprzez, niespodzianka, Thread.Interrupt/interrupt. Wówczas przy następnym wejściu do funkcji czekającej w rodzaju Thread.Sleep/sleep rzucany jest wyjątek (Thread)InterruptedException, który odwija stos wątku, kończąc w ten sposób jego działanie. W natywnych platformach nie może być oczywiście dokładnego odpowiednika podobnej operacji, lecz można ją symulować czekaniem na jakimś obiekcie synchronizacyjnym i sygnalizowaniem go, gdy chcemy to czekanie przerwać. W POSIX-ie funkcja pthread_cancel działa aczkolwiek w dość podobny sposób do Interrupt, pozwalając na posprzątanie zasobów w trakcie anulowania wątku.
  3. Najmniej eleganckim sposobem jest wreszcie brutalne przerwanie wątku, czyli próba jego natychmiastowego zakończenia w dowolnym momencie. Zarówno .NET, jak i Java rozwiązały to w podobny sposób: po wywoływaniu odpowiedniej metody (Thread.Abort/stop), w wątku rzucany jest bardzo specjalny wyjątek (ThreadAbortException lub ThreadDeath). Ma on tę cechę, że… przechodzi przez (prawie) wszystkie bloki catch – w .NET właściwie nie można go “zdusić”. To jednak sprawia, że taki wyjątek może wykonać po drodze wszystkie bloki finally, co pozwala zwolnić zawłaszczone zasoby i blokady oraz przywrócić obiekty synchronizujące do właściwego stanu. Występująca w Windows API funkcja TerminateThread takiej możliwości już nam nie daje, przez co jej użycie może prowadzić do różnych kłopotów.

Wnioski z tych trzech sposobów są mniej więcej takie, że pisząc kod wykonywany w osobnym wątku powinniśmy zawsze przewidzieć “czysty” sposób na jego zakończenie. Poleganie na przerywaniu lub brutalnym zakańczaniu wątków może się bowiem źle skończyć.

Tags: , ,
Author: Xion, posted under Programming » 2 comments

Pomijanie klawiszy w C#

2008-04-21 19:50

Czasami dobrze jest reagować na wszystkie przychodzące do okna wciśnięcia klawiszy w jednym miejscu. W C# ustawia się wtedy właściwość KeyPreview dla formy. Sprawia to, że dostaje ona zdarzenia klawiatury niezależnie od tego, która z potomnych kontrolek ma aktualnie fokus. Dzięki temu można implementować polecenia aktywowane wciśnięciami określonych klawiszy lub ich kombinacji, których nie da się przypisać np. do skrótów klawiszowych w menu:

  1. private void MainForm_KeyDown(object sender, KeyEventArgs e)
  2. {
  3.     switch (e.KeyCode)
  4.     {
  5.         case Keys.Oemtile:
  6.             // tylda - pokaż/ukryj konsolkę
  7.             txtConsole.Visible = !txtConsole.Visible;
  8.             break;
  9.     }
  10. }

Jeśli jednak nic w tej kwestii nie zrobimy, zdarzenia klawiatury domyślnie i trafią do kontrolki aktywnej, co niekoniecznie musi nam się podobać. W powyższym przykładzie nasza prosta konsolka “złapałaby” na przykład wszystkie wciśnięcia klawisza tyldy jako znaki ~.
Jak temu zapobiec? Wystarczy wykorzystać właściwość SuppressKeyPress z klasy KeyEventArgs:

  1. e.SuppressKeyPress = true;

To sprawi, że zdarzenie wciśnięcia klawisza nie będzie przekazywane dalej i żadna kontrolka go nie otrzyma. To przydatne, jeśli mamy ręcznie zakodowane skróty klawiszowe, które pokrywają się ze standardowymi sekwencjami.

Tags:
Author: Xion, posted under Programming » Comments Off on Pomijanie klawiszy w C#

Dodatki do kodu

2008-01-16 10:34

Komentarze umieszczamy w kodzie, aby opisać jego działanie. Są one przeznaczone wyłącznie dla osób czytających go i jedynie programy typu javadoc czy doxygen – służące automatycznemu generowaniu dokumentacji – mogą się niektórymi komentarzami interesować. Na pewno jednak nie robi tego kompilator.
Wymyślono jednak, że niektóre elementy kodu potrzebują innych, specyficznych “komentarzy”, przeznaczonych dla kompilatora właśnie. Różnie się one nazywają w różnych językach, ale ich głównym celem jest przekazanie dodatkowych informacji odnośnie klasy, metody, typu czy innego elementu programu, bez potrzeby stosowania.

W .NET takie dodatki nazywa się atrybutami i umieszcza przed wybraną deklaracją, w nawiasach kwadratowych (lub kątowych w przypadku Visual Basica), oddzielone przecinkami. Atrybuty te mogą dotyczyć właściwie wszystkiego, począwszy od wskazania na klasę, która może być serializowana (i pola, które nie powinny być) po informacje dla Form Designera na temat danej właściwości niestandardowego komponentu:

  1. [Category("Foos"), Description("A foo associated with the component")]
  2. public Foo Foo
  3. {
  4.    get { return foo; }
  5.    set { foo = value; }
  6. }

Ogólnie dotyczą one jednak różnych funkcji samej platformy .NET i służą wskazaniu, które elementy pełnią określone role w różnych rozwiązaniach, które działają w jej ramach. Można aczkolwiek definiować także własne atrybuty, a potem w czasie działania programu wydobywać o nich informacje przy pomocy mechanizmu refleksji.

A jak to wygląda w Javie? Otóż tam od wersji 1.5 istnieją adnotacje. Ich nazwy poprzedza się znakiem @ i umieszcza w osobnych linijkach, poprzedzających deklaracje, których dotyczą. Ponieważ tutaj język jest ściśle związany z platformą (maszyną wirtualną Javy), adnotacje czasami pełnią funkcje “brakujących” słów kluczowych lub dyrektyw dla kompilatora. Typowy przykład to adnotacja Override:

  1. @Override
  2. public boolean equals(Object obj)  { /* ... */ }

którą możemy oznaczać przesłonięte wersje metod wirtualnych. Jest to praktyczne, gdyż w przypadku popełnienia błędu (np. literówki w nazwie) kompilator ostrzeże nas, że tak naprawdę zdefiniowaliśmy zupełnie nową metodę (bez tego błąd objawiłby się dopiero nieprawidłowym działaniem programu).
Naturalnie, możliwe jest też tworzenie własnych adnotacji oraz pobieranie informacji o nich przy pomocy refleksji. Aż korci, żeby sprawdzić, kto od kogo ściągał tutaj pomysły ;-)

W C++ deklaracje standardowo nie mają żadnych “ozdobników”, ale pod tym względem w różnych kompilatorach bywa różnie. Na przykład w Visual C++ mamy słówko __declspec, które służy do całego mnóstwo różnych celów. Wśród nich są chociażby takie oto warianty:

  • __declspec(align(n)) służy do określania, jak dane (np. pola struktur) mają być wyrównane w pamięci. Dzięki temu będą one umieszczone tak, by zajmowały zawsze wielokrotność podanych n bajtów, co przy odpowiedniej wartości (np. 32) może zwiększyć wydajność lub (dla 1) zmniejszyć zajętość pamięci.
  • __declspec(deprecated) pozwala oznaczyć dany element kodu jako przestarzały. Jego użycie będzie skutkowało wtedy ostrzeżeniem kompilatora.
  • __declspec(dllexport) i __declspec(dllimport) służą do tworzenia symboli eksportowanych w bibliotece DLL i do importowania tych symboli w innym programie.
  • __declspec(property) wprowadza konstrukcję właściwości do klasy, bardzo podobną do tych obecnych w Delphi. Po podaniu jednej lub dwóch metod dostępowych (do odczytu i ew. zapisu), otrzymujemy właściwość o danej nazwie i typie. Jaka szkoda, że to nieprzenośne :)

Zasadniczo Visual C++ posiada też atrybuty podobne do .NETowych, które są konieczne do tworzenia interfejsów COM. Na szczęście nimi, jak i samym COM-em, nie trzeba już sobie zaprzątać głowy :)

Tags: , , , ,
Author: Xion, posted under Programming » 2 comments

Bolączki C++ #4 – Właściwości

2007-09-29 13:14

W teorii OOPu klasa może składać się wielu różnych rodzajów elementów. Mamy więc pola, metody, właściwości, operatory, typy, zdarzenia czy nawet sygnały (cokolwiek to oznacza). Z drugiej reprezentacja obiektu w pamięci operacyjnej działającego programu to niemal wyłącznie wartości jego pól (z drobną poprawką na ewentualną tablicę metod wirtualnych).
To są dwie skrajności, a między nimi mam wszystkie obiektowe języki programowania. Jedne oferują w tym zakresie więcej, inne mniej. Weźmy na przykład C++.

Oprócz niezbędnych pól i metod pozwala on definiować przeciążone operatory i typy wewnętrzne. Nie posiada za to niezwykle przyjemnego “cukierka składniowego”, czyli właściwości. Z punktu widzenia programisty właściwości to takie elementy interfejsu klasy, który wyglądają jak pola. Różnica polega na tym, że dostęp do właściwości nie musi oznaczać bezpośredniego odwołania do pamięci, lecz może mu towarzyszyć dodatkowy kod – na przykład sprawdzający poprawność ustawianej wartości.
Prawdopodobnie najbardziej elastyczny mechanizm właściwości wśród popularnych języków programowania ma C#. Tam kod wykonywany przy pobieraniu i ustawianiu właściwości pisze się bezpośrednio w jej deklaracji:

  1. class Fraction
  2. {
  3.    private int num;
  4.    private int denom;
  5.  
  6.    // właściwość - mianownik ułamka
  7.    public int Denominator
  8.    {
  9.       get { return denom; }
  10.       set
  11.       {
  12.          if (value == 0)   throw new DivideByZeroException();
  13.          denom = Math.Abs(value);
  14.          num *= Math.Sign(value);
  15.       }
  16.    }
  17. }

Nieco gorzej jest w Delphi czy Visual C++, gdzie istnieje deklaracja __declspec(property). Tam trzeba napisać odpowiednie metody służące pobieraniu/ustawianiu danej wartości (akcesory) i wskazać je w deklaracji właściwości.
Natomiast w czystym C++ rzeczone akcesory – metody Get/Set – stosowane bezpośrednio są jedynym wyjściem. Niezbyt ładnym rzecz jasna.

Bez właściwości można się obyć, a ich wprowadzenie do języka pewnie nie byłoby takie proste. Pomyślmy na przykład, jak miałyby się one do wskaźników i referencji: o ile pobranie adresu właściwości nie ma sensu, o tyle przekazywanie jej przez referencję byłoby z pewnością przydatne.
Dlatego chociaż akcesory wyglądają brzydko, pewnie jest przez długi czas będą jedyną opcją. Na pocieszenie dodam, że programiści Javy są pod tym względem w identycznej sytuacji :)

Tags: , ,
Author: Xion, posted under Programming » 3 comments
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.