03/09/2009

ESRI WPF/Silverlight API

Home

ESRI to lider światowego rynku systemów informacji geograficznej (ang. GIS). Lider przez duże L - Na całym świecie z rozwiązań ESRI korzysta 300 tyś instytucji w tym 2/3 firm z listy Fortune. ESRI to taki Microsoft w świecie GIS'ów :). Czym jednak są systemy GIS'owe?

GIS to w bardzo dużym skrócie system służący do wprowadzania, przechowywania, przetwarzania, analizowania i wizualizowania danych przestrzennych. Upraszczając jeszcze bardziej, a wręcz trywializując chodzi tu po prostu o mapę ;) Niektórzy zapewne pomyślą coś w rodzaju: "Czym ty zawracasz głowę człowieku, przecież jest już Google Maps". Nie obrażając nikogo Google Maps w porównaniu z produktami ESRI to zabawka dla dzieci, którą poważni ludzie się nie zajmują. Patrząc na to z innej strony oferta ESRI (w jednym z kolejnych postów postaram się ją przybliżyć) jest dużo, dużo, dużo bogatsza. Poza tym jest skierowana do zupełnie innego rodzaju odbiorców, którzy poza wizualizacją danych geograficznych chcą je analizować i przetwarzać w celu usprawnienia swoich procesów biznesowych.

Czemu jednak w ogóle o tym piszę? Otóż rozwiązania ESRI są generalnie bardzo drogie i dostępnie dla niewielkiego grona specjalistów, a co z tym związane mało znane. Ostatnio dotarłem jednak do darmowego (dla celów niekomercyjnych) API dla WPF/Silverlight umożliwiającego tworzenie całkiem fajnych aplikacji. Tworzone aplikacje możemy zasilić danymi udostępnianymi przez ESRI poprzez ArcGIS Online. Zasoby udostępnione publicznie nie są tak duże jak w przypadku Google ale można już coś zdziałać przy ich pomocy. Możliwe jest również wykorzystanie map publikowanych przez Microsoft na Bing Maps.

Potrzebne biblioteki można pobrać tutaj, a dokumentację znajdziemy tutaj. Początkowo chciałem opisać prosty przykład użycia ale zrezygnowałem z tego kiedy zobaczyłem tą stronę. Znajdziecie tam kilkanaście bardzo prostych, poglądowych aplikacji. Polecam.

26/08/2009

Przykład tego jak nie należy pisać kodu

Home

Czasami jak patrzę na niektóre kody to zastanawiam się o czym myślał piszący je programista. Ostatnio natknąłem się na kod mniej więcej jak poniżej (zmieniłem nazwy zmiennych, metod itd.). Pomińmy jaka jest jego logika i gdzie został użyty. Proponuję natomiast przyjrzeć się przez chwilę temu fragmentowi i spróbować odpowiedzieć na pytanie co jest nie tak.
...
for (int i = 0; i < values.Length; i++)
{
  if (values[i] != null)
  {
    if (hash[values[i]] != null)
      Process(hash[values[i]]);

    if (hash[values[i]] != null)
    { 
      Process(values[i]);
      Process(values[i], hash[values[i]]);
      hash.Remove(values[i]);
    }
  }
}
...
Mnie uderzyła duża i zupełnie zbyteczna liczba odwołań do zmiennych values (tablica) i hash (Hashtable). Odwołanie do tej pierwszej występuje 8 razy, a do drugiej 5 razy! Po chwili pracy i wprowadzeniu bardzo prostych poprawek kod wygląda tak:
...
for (int i = 0; i < values.Length; i++)
{
  string val = values[i];
  if (val != null)
  {
    object obj = hash[val];
    if (obj != null)
      Process(obj);

    if (obj != null)
    {
      Process(val);
      Process(val, obj);
      hash.Remove(obj);
    }
  }
}
...
Odwołanie do zmiennych values oraz hash występuje tylko 1 raz. Czemu o tym piszę? Odpowiedź jest tak prosta jak wprowadzone poprawki: wydajność. Dodam jescze, że kod ten można jeszcze ulepszyć. W tym poście skupiam się jednak na tej jednej rzeczy.

W tym przypadku poprawiona wersja działa jakieś 3x/4x razy szybciej! Przyznam jednak, że całościowy efekt tej optymalizacji jest niezauważalny z dwóch powodów. Po pierwsze długość używanej w kodzie tablicy i liczba elementów w tablicy hashującej jest generalnie niewielka. Przeważnie nie przekracza kilkudziesięciu, może kilkuset elementów. W takim scenariuszu czas wykonania tego kodu nie przekracza milisekundy. Po drugi częstotliwość użycia tego kodu jest niewielka.

Z drugiej jednak strony programista, który napisał omawiany kod stosuje (zastosował) takie konstrukcje w wielu miejscach. Być może wywołuje jedną, ciężką, metodę wielokrotnie zamiast zapamiętać jej wynik wywołania na później. Przykładów takich można mnożyć.

Reasumując należy uczyć się na cudzych błędach i strzec się takich konstrukcji.

18/08/2009

Ciekawy przypadek optymalizacji

Home

Ostatnio zajmowałem się optymalizacją aplikacji mapowej do planowania i inwentaryzacji sieci telekomunikacyjnych. Problem polegał na tym, że przy dużej liczbie warstw aplikacja zaczynała działać powoli. Warstwa to taki pojemnik na dane tego samego rodzaju np.: warstwa kabli, warstwa wzmacniaczy itd.

W określonych scenariuszach liczba takich warstw mogła sięgać nawet kilku tysięcy. Zacząłem od wrzucenia aplikacji do profilera (AQTime). Jak to często bywa w takich sytuacjach okazało się, że czas pożera kilka metod, które same z siebie wykonują się bardzo szybko ale są wołane bardzo dużo razy - proporcjonalnie do liczby rastrów. W rzeczywistości dojście to tego o jakie metody chodzi nie było oczywiście takie proste ale nie będę zanudzał szczegółami.

Po pierwsze postanowiłem ograniczyć liczbę wywołań tych metod. Niestety okazało się, że z różnych powodów nie da się tego zrobić. W drugim podejściu postanowiłem, więc ograniczyć czas potrzebny na ich wykonanie. Co ciekawe okazało się, że metody te pozornie prawie nic nie robią czyli zawierają tylko odwołania do właściwości Count czy też indeksera jakiejś kolekcji.

Zanim przejdę dalej wyjaśnię, że jako platformę mapową używam rozwiązań firmy ESRI opartych o technologię COM. W związku z tym konieczne jest użycie mechanizmów interoperacyjności pomiędzy kodem zarządzanym i niezarządzanym. W omawianym przypadku wspomniana kolekcja nie była kolekcją zarządzaną ale wrapper'em na obiekt COM. Postanowiłem, więc zrezygnować z odwołań do tej kolekcji i użyć dobrze znanej klasy List<T>. W praktyce utworzyłem instancję listy zarządzanej istniejącą równolegle do kolekcji niezarządzanej. Elementy do obu kolekcji dodawane są równocześnie, podobnie usuwane. Nie jest to problem ponieważ czynność ta wykonywana jest jednorazowo.

Postępując podobnie w kilku innych miejscach, to znaczy eliminując odwołania do wrapper'ów do obiektów COM, zmniejszyłem czas wykonywania niektórych czynności o połowę! Wydaje mi się, że to niezły wynik szczególnie, że dalsze optymalizację cały czas są możliwe. Wniosek z tego taki, że czasem warto wprowadzić redundancję aby zyskać na wydajności.

14/08/2009

Site, Site Collection, IIS Web Site...

Home

Oj dużo miałem ostatnio na głowie: ślub, wesele, podróż poślubna :) ale pora wrócić do blogowania. Do tej pory nie miałem do czynie z WSS (Windows SharePoint Services) ale los chciał, że przyjdzie mi poznać również tą technologię Microsoftu. Przygodę w świecie Sharepoint'a postanowiłem zacząć od znalezienia i zebrania serwisów, blogów i książek dotyczących tego zagadnienia. Jak się szybko okazało pierwszym problemem okazała się terminologia. Doświadczony developer WSS skwitowałby to pewnie ironicznym uśmieszkiem ale dla mnie terminologia WSS jest co tu dożo mówić 'zakręcona'. Jedną z przyczyn jest zapewne fakt, że jako developer ASP.Net widząc słowo site od razu przychodzi mi do głowy projekt typu Web Site, podobnie Web Application to również dla mnie typ projektu VS. W nomenklaturze WSS terminy te przyjmują natomiast inne znaczenia. Postanowiłem więc opisać pojęcia, które wprawiły mnie w taką konsternację.
  • IIS Web Site - Punkt wejściowy do serwera IIS. Domyślny site (Default Web Site) oczekuje na żądania HTTP na porcie 80. Nie mylić z projektem typu Web Site w VS. Projekt typu Web Site po zainstalowaniu na IIS widoczny jest najczęściej jako katalog wirtualny.
  • Web Application - IIS Web Site przygotowana do pracy z WSS. Nie mylić z projektem typu Web Application w VS. Projekt typu Web Application po zainstalowaniu na IIS widoczny jest najczęściej jako katalog wirtualny.
  • Virtual Server - W WSS 2.0 oznacza to samo co Web Application.
  • Virtual Directory - Katalog wirtualny. Tworzy podprzestrzeń adresów URL w ramach IIS Web Site w jakiej został utworzony. Katalogi wirtualne mapują się na fizyczne lokalizacje na dysku.
  • Site Collection - Podstawowa jednostka organizacyjna służąca do grupowania i zarządzania site'ami. Web Application może składać się z wielu takich kolekcji. Na poziomie programistycznym reprezentowana przez klasę SPSite.
  • Site - W WSS 3.0 to porostu pojemnik na jakąś zawartość: listy, biblioteki dokumentów czy też inne pojemniki. Każdy site musi należeć do jakiejś kolekcji. Na poziomie programistycznym reprezentowany przez klasę SPWeb. W WSS 2.0 termin ten oznacza to samo co Site Collection. Stąd w WSS 3.0 klasa reprezentująca kolekcję site'ów nazywa się SPSite, a nie SPSiteCollection.
  • Web - Stary termin z WSS 2.0 oznaczający to samo co site w WSS 3.0. Stąd w WSS 3.0 klasa modelująca site nazywa się SPWeb, a nie SPSite.
  • Top-level Site - Główny pojemnik (site) w ramach kolekcji pojemników (site collection).
  • Root Web - W WSS 2.0 oznacza to samo co Top-level Site.

22/07/2009

Czemu należy używać właściwości SyncRoot?

Home

SyncRoot to właściwość zdefiniowana na poziomie interfejsu ICollection służąca do synchronizowania operacji wykonywanych na kolekcjach przy pomocy słowa kluczowego lock lub jawnie przy pomocy monitora. Czemu jednak należy używać tej właściwości zamiast instancji kolekcji, czyli czemu zalecany jest taki kod:
lock(list.SyncRoot)
{
   ...
}
A nie taki?
lock(list)
{
   ...
}
Kiedy postawiłem sobie to pytanie okazało się, że odpowiedź nie jest dla mnie oczywista. Wizyta w dokumentacji MSDN nic nie pomogła bo udzielone wyjaśnienie należy do grupy tych, które stają się jasne i oczywiste jak już znasz prawidłową odpowiedź ;).

Spędziłem trochę czasu zastanawiając się nad tą kwestią oraz szperając po sieci i doszedłem do następujących wniosków. Należy używać właściwości SyncRoot ponieważ zapewnia ona centralny, dobrze określony, bezpieczny punkt synchronizacji dla kodu zewnętrznego używającego kolekcję jak i dla kodu wewnętrznego kolekcji. Inaczej mówiąc zapewnia to, że każdy kod używa tego samego obiektu do synchronizacji. Bezpieczny w tym sensie, że bardzo łatwo można stwierdzić kto i kiedy uzyskuje dostęp do obiektu synchronizacyjnego - wystarczy ustawić pułapkę w getter-ze właściwości. Synchronizacja bezpośrednio przy użyciu instancji kolekcji czy też na obiekcie this może natomiast doprowadzić to bardzo trudnych do wykrycia zakleszczeń ponieważ nie będzie można łatwo określić kto i w jakim momencie blokuje obiekt. Z drugiej strony jeśli taka semantyka SyncRoot jest z jakiegoś powodu niepożądana kolekcje wydziedziczone z kolekcji bazowej mogą dostarczyć własnej implementacji tej właściwości.

17/07/2009

$find vs $get

Home

Ostatnimi czasy pracowałem nad webową aplikacją mapową, która bardzo silnie wykorzystuje ASP.NET Ajax, a więc nie obyło się bez napisania "kilku" linii kodu w JavaScript. Przy okazji implementowania jednej z funkcjonalności musiałem odwołać się do kontrolki mapowej. Niewiele myśląc napisałem mniej więcej taki kod:
...
var map = $get(MapControlId);
...
Kontrolka oczywiście została znaleziono. Zgodnie z dokumentacją powinna udostępniać funkcję toMapPoint konwertującą współrzędne ekranowe na mapowe. Niestety ale okazało się, że taki kod nie działa ponieważ znaleziona kontrolka nie udostępnia takiej metody.
...
var res = map.toMapPoint(x, y);
...
Moment konsternacji, dokumentacja mówi jedno, a życie pokazuje drugie. Chwila szukania w Internecie i rozwiązanie problemu jak zwykle okazało się trywialne. Zamiast funkcji $get należy użyć funkcji $find. Czym się różnią? Pierwsza z nich to po prostu skrót do użycia dobrze znanej funkcji getElementById służącej do wyszukiwania elementów DOM o podanej wartości atrybutu id.

Funkcja $find służy natomiast do wyszukiwania komponentów o stanowi skrót dla wywołania findComponent. Komponent to moduł programowy udostępniający jakąś funkcjonalność. Komponenty dzielimy na trzy kategorie. Po pierwsze mamy komponenty niewizualne takie jak zegar (ang. timer). Pozostałe dwa typy komponentów w ASP.NET Ajax to kontrolki czyli komponenty wizualne (elementy DOM) oraz zachowania (ang. behaviours), które rozszerzają możliwości istniejących już elementów DOM. Metoda, której potrzebowałem użyć była oczywiście zdefiniowana na komponencie, a nie na elemencie DOM.

03/07/2009

'ltr' -> 'rtl'

Home

Wszyscy jesteśmy przyzwyczajeni do pisania i czytania od lewej do prawej. Przyjmujemy jako pewnik, że menu Start znajduje się w lewym dolnym rogu ekranu, krzyżyk służący do zamykania okna w prawym górnym rogu okna, a krzyżyki służące do rozwijania węzłów w drzewie umieszczane są z lewej strony węzłów itd. Dlaczego o tym piszę? Niedawno zajmowałem się przystosowaniem aplikacji WWW do kultur o orientacji od prawej do lewej. Na warsztat wziąłem kulturę hebrajską. Zainstalowałem odpowiedni pakiet do Windows XP i po przełączeniu ustawień regionalnych i językowych opadła mi szczęka :) Wszystko zostało odwrócone: ikony na pulpicie, położenie poleceń w menu, przycisk Backspace działa jak Delete, po naciśnięciu strzałki w prawo kursor przesuwa się w lewo, menu start jest w prawym dolnym rogu, strzałka do rozwijania list z lewej strony itd. Mały przykład poniżej:



Wracając jednak do aplikacji WWW. Okazuje się, że przestawienie aplikacji w tryb Right to Left jest banalnie proste i sprowadza się do dodania atrybutu dir o wartości 'rtl' na przykład do tagu html. Całą resztą zajmie się przeglądarka (testowałem w IE7 oraz Operze). Poniżej porównanie prostego układu w dwóch orientacjach. Jak widać układ z prawej strony stanowi lustrzane odbicie tego z lewej.



W dużej, komercyjnej aplikacji po zmianie orientacji zauważyłem niewielkie błędy ale można je z pewnością łatwo wyeliminować. W każdym razie wkładając minimum wysiłku można bardzo prosto przełączyć aplikację WWW w tryb od prawej do lewej.

18/06/2009

Xslt jako język programowania

Home

Ostatnio sporo czasu poświęcam poznawaniu nowych narzędzi w Visual Studio 2010 czyli pożytecznej zabawie. Przy tej okazji uczę się również czasami czegoś na temat starszych wersji tego środowiska. Na przykład niedawno zajmowałem się narzędziami wspierającymi pracę z szeroko pojętym Xml'em, a w szczególności z transformacjami Xsl. Obok rzeczy wręcz oczywistych takich jak podświetlenie składni, podpowiadanie - IntelliSense znajdziemy również: profiler, debugger czy podgląd wyniku działania transformacji. Co ciekawe narzędzia te, oprócz profilera, znajdują się również w Visual Studio 2005/2008. Muszę przyznać, że do tej pory nie zdawałem sobie sprawy z tego, że Visual Studio traktuje Xslt jako "normalny" język programowania. Co tu dużo mówić, człowiek uczy się całe życie.

Dla tych co żyli w niewiedzy tak jak ja kilka zdań na ten temat. Zaczynamy od otworzenia pliku z definicją transformacji (z rozszerzeniem *.xslt) albo od dodania go do projektu. We właściwościach otwartego dokumentu (zakładka Properties) mamy do wypełnienia dwa pola: Input oraz Output. W pierwszym wskazujemy dokument Xml do przekształcenia, a w drugim plik wynikowy.



Analogicznie można zacząć od otworzenia pliku Xml. W tym przypadku na zakładce Properties będziemy mieli do wypełninia pola: Stylesheet oraz Output. W pierwszym wskazujemy plik z transformacją, a znaczenie drugiego jest takie same jak wcześniej.

W momencie kiedy mamy otworzony plik z transformacją albo plik Xml na pasku menu pojawi się element o nazwie XML.



Z tego menu może po pierwsze wybrać polecenie Show XSLT Output, które odpali transformację i pokaże nam jej wynik. Dużo ciekawsze jest jednak polecenie Debug XSLT, które umożliwia śledzenie wykonania transformacji.

Pułapki umieszczamy w kodzie transformacji dokładnie w taki sam sposób jak w kodzie programu napisanego w C#. Co więcej pułapki możemy też postawić w dokumencie Xml. Funkcji tej można użyć kiedy interesuje nas ściśle określony węzeł w dokumencie i chcemy aby debugger zatrzymał się kiedy będzie przetwarzany. Bardzo przydatne jest to, że na bieżąco możemy obserwować jak generowany jest plik wynikowy. Kod transformacji lub dokument Xml możemy teoretycznie modyfikować w czasie działania transformacji ale nie odniesie to żadnych skutków.



W Visual Studio 2010 pojawiła się jeszcze możliwość profilowania transformacji (w menu XML dodane zostało polecenie Profile XSLT). Możemy sprawdzić, wykonanie której część kodu transformacji zajmuje najwięcej czasu itd. Okno z raportem z wynikami profilowania zostało przedstawione poniżej:


04/06/2009

Bardzo użyteczne narzędzie do pracy z WMI

Home

Przeglądając dzisiaj fora internetowa, dotyczące platformy .NET, w odpowiedzi na jedno z zadanych pytań znalazłem wzmiankę o bardzo użytecznym narzędziu WMI Code Creator v1.0. Narzędzie to pozwala na wygenerowanie kodu używającego WMI (ang. Windows Management Instrumentation) do wykonywania różnego rodzaju zadań zarządzania: odczytywanie danych, oczekiwanie na zdarzenia WMI czy wywoływanie metod z klas WMI.

WMI nie jest za pewne narzędziem używanym w codziennej pracy ale mogącym się czasem przydać. Przy pomocy tej technologii możemy dobrać się do szczegółowych inforamacji na temat BIOS'u, procesora, dysków i innych urządzeń, procesów, usług oraz najróżniejszych ustawień systemu operacyjnego. WMI posługuje się dobrze znanymi pojęciami takimi jak: przestrzenie nazw, klasy, metody, zdarzenia i właściwości. Przestrzenie nazw zawierają klasy dotyczące poszczególnych obszarów zarządzania. Klasy modelują różne byty w tych obszarach. Właściwości tych klas to różne parametry konfiguracyjne. Na przykład dla klasy modelującej procesor możemy odczytać jego rodzinę, producenta czy liczbę rdzeni. Dla klasy modelującej proces została zdefiniowana metoda pozwalająca go zamknąć. Przykładem zdarzenia na jakie można oczekiwać jest natomiast zmiana statusu usługi.

Z poziomu platformy .NET do komunikacji z WMI służą klasy z przestrzeni nazw System.Management. Ich użycie nie jest bardzo skomplikowane, główna trudność polega na mnogości przestrzeni nazw i klas WMI. Nie sposób tego spamiętać. Tutaj z pomocą przychodzi wspomniane narzędzie. Przy jego pomocy możemy wybrać interesującą nas przestrzeń, potem klasę i jej właściwości, a następnie wygenerować kod w jednym z trzech języków: C#, Visual Basic .NET, Visual Basic Script, który odczyta parametry jakie wybraliśmy, wywoła metodę lub będzie oczekiwał na zdarzenia WMI. Co bardzo przydatne WMI Code Creator v1.0 pozwala podejrzeć wartości wybranych właściwości.

02/06/2009

Problemy z instalatorem

Home

A problem has been encountered while loading the setup components. Canceling setup

Komunikat ten, objaśniający bardzo dokładnie przyczynę błędu ;), napotkałem próbując doinstalować do Visual Studio 2008 Team System komponenty związane z obsługą języka C++. Rozwiązanie problemu okazało się trywialne ale dojście do niego zajęło mi trochę czasu.

Rozwiązywanie problemu rozpocząłem od sprawdzenia czy w katalogu z plikami instalacyjnymi znajdują się inne pliki z setup w nazwie lub podobne i czy można je uruchomić. Kiedy to nie pomogło skopiowałem cały katalog z instalatorem i potrzebnymi plikami na dysk lokalny przyjmując, że problem może wynikać z tego, że uruchamiam go z dysku zdalnego. Niestety ale to również nie pomogło.

Zacząłem szukać pomocy u pana Google. Natrafiłem na forum, na którym radzono aby po prostu odinstalować środowisko i wszystkie jego komponenty, a następnie zainstalować je jeszcze raz. Pewnie by podziałało ale nie chciałem poświęcać cennego czasu na ponowne instalowanie wszystkiego czego potrzebuję.

Po dalszych poszukiwaniach znalazłem wskazówkę mówiącą abym poszukał w katalogach tymczasowych logów instalatora i zobaczył co się w nich znajduje. Rada była o tyle mało użyteczna, że nie zawierała informacji gdzie dokładnie szukać plików i jak się nazywają. Uruchomiłem więc Process Monitor i zacząłem przeglądać jakie pliki otwiera instalator VS. W moim przypadku nazywały się: dd_install_vs_vstdcore_90.txt oraz dd_error_vs_vstdcore_90.txt i znajdowały się w katalogu:
C:\Documents and Settings\username\Local Settings\Temp
W jednym z plików znalazłem wpisy zawierające tekst ERRORLOG EVENT, coś w tym rodzaju:
[06/02/09,12:25:45] setup.exe: ***ERRORLOG EVENT*** : ISetupModule::SetManager() failed in ISetupManager::LoadSetupObjectGuid() : vs_setup.dll
Nie zastanawiając się długo wrzuciłem treść loga do Googla i jak to zwykle otrzymałem dużoooooooo wyników. Jedna z pierwszych stron, zresztą forum MSDN, dotyczyła VS 2005 ale wyglądała obiecująco. Wyczytałem na niej, że problem ustępuje po wyłączeniu antywirusa, a dokładniej programu firmy Kaspersky. Korzystam co prawdy z produktu konkurencji ale stwierdziłem, że nie zaszkodzi spróbować. Niestety ale i to rozwiązanie okazało się chybione. Kiedy już prawie straciłem nadzieję kilka pozycji niżej na liście wyników znalazłem ten post. Żeby nie przedłużać rozwiązanie problemu to:

W przypadku Visual Studio 2008 z zainstalowanym service pack'iem pierwszym (nie wiem jak z kolejnymi) instalator należy uruchamiać z poziomu okna Add or Remove Programs w panelu sterowania.

Data Binding i dobre praktyki programistyczne

Home

The data source for GridView with id 'GridView1' did not have any properties or attributes from which to generate columns. Ensure that your data source has content.

Czy spotkaliście się z takim błędem pomimo, że byliście pewni, że poprawnie zasilacie kontrolkę lub powiązane z nią źródło danych? Jeśli tak to problem był związany z użytymi strukturami danych. Przykładowy kod, który spowoduje powyższy błąd został przedstawiony poniżej. Zacznijmy od prostej klasy, którą chcemy zaprezentować na kontrolce GridView.
public class Data
{
  public int Id;
  public string Name;
}
Fragment kodu strony:
...
<asp:GridView ID="GridView1" runat="server">
</asp:GridView>
...
Kod zasilający kontrolkę jest równie prosty, dla uproszczenie nie korzystam ze źródła danych:
...
this.GridView1.DataSource = new Data[] { new Data { Id = 1, Name = "1" }, new Data { Id = 2, Name = "2" } };
this.GridView1.DataBind();
...
Uważny czytelnik mógł zauważyć, że w klasie Data użyłem publicznych pól składowych zamiast właściwości. I tu leży pies pogrzebany. Pola składowe klasy nie są automatycznie wykrywane i obsługiwane przy dowiązywaniu kontrolki (ang. binding). Innymi słowy pola trzeba opakować we właściwości. Nie wiem czy to przeoczenie w implementacji czy celowe działanie (biorąc pod uwagę treść komunikatu to drugie) ale w każdym razie ograniczenie to wspiera dobre praktyki programistyczne. Poprawny kod klasy Data powinien wyglądać tak:
public class Data
{
  public int Id { get; set; };
  public string Name { get; set; };
}

25/05/2009

Konkurs Code Camp 2009 - podsumowanie

Home

Na podsumowanie konferencji Code Camp 2009 w Warszawie z mojej perspektywy nadejdzie jeszcze czas. Najpierw chciałbym napisać o konkursie, który udało mi się zorganizować przy tej okazji. W skrócie konkurs składał się z dwóch etapów. Pierwszy etap (tzw. konkurs skojarzeniowy) odbył się w tygodniu poprzedzającym konferencję i polegał na odgadnięciu nazwy miejsca (kraju, miasta, wyspy itd.) na podstawie zdjęć, obrazków czy też opisu słownego. Wszystkie te miejsca miały tą wspólną cechę, że ich nazwy zostały użyte jako nazwy kodowe różnych produktów firmy Microsoft. Drugi etap, konkurs kartkowy, odbył się w czasie konferencji i polegał na odpowiedzeniu na 5 testowych pytań. Odpowiadając na pytanie nie można było korzystać z komputera, Internetu czyli trzeba było liczyć tylko na własną głowę.

Jak wyszło? Mówiąc szczerze nie jest do końca zadowolony. Frekwencja co tu dużo mówić nie dopisała. W konkursie wzięło udział tylko 6 osób. Chociaż z tego co dowiedziałem się od bardziej doświadczonych kolegów to i tak całkiem dobry wynik :)

Jeśli chodzi o konkurs skojarzeniowy to okazał się on dość łatwy i generalnie uczestnicy nie mieli problemów z odpowiedzeniem na pytania. Z konkursem kartkowym było już gorzej, poprawnych odpowiedzi było zdecydowanie mniej. Wydaje mi się, że było to spowodowane dwoma czynnikami. Po pierwsze pytania mogły być zbyt szczegółowe ale ja nie jestem tutaj obiektywny. Po drugie, konkurs był przewidziany na raptem 10 - 15 minut. Najwyraźniej nie udało mi się dostosować poziomu pytań do tak krótkiego czasu. Muszę o tym pamiętać i następnym razem przetestować pytania na znacznie większej grupie ludzi niż to uczyniłem.

Poniżej zamieszczam pytania i odpowiedzi do obu konkursów:

Konkurs skojarzeniowy


Odpowiedzi

  1. Dublin - Opis
  2. Hawaii - Visual Studio
  3. Astoria - ADO.NET Data Services
  4. Kilimanjaro - Sql Server 2010/Dynamics CRM 4.0
  5. Orleans - Opis
  6. Geneva - Opis
  7. Paris - Opis
  8. Ibiza - Sync Framework
  9. Oslo - Opis
  10. Quebec - Windows Embeded 2010
Do trzeciego pytani wkradł się błąd. Pytałem w nim o miasto na wschodnim wybrzeżu podczas gdy chodziło o zachodni brzeg USA. Z tego powodu zaliczałem również inne odpowiedzi na przykład Boston czyli Visual Studio 97.

Konkurs kartkowy

1. Mamy definicje dwóch klas jak poniżej:
class Base
{
   public virtual void Fun(int x)
   {
      Console.WriteLine("Base.Fun(int x)");
   }
}

class Derived : Base
{
   public override void Fun(int x)
   {
      Console.WriteLine("Derived.Fun(int x)");
   }

   public void Fun(object o)
   {
      Console.WriteLine("Derived.Fun(object o)");
   }
}
Co wypisze na ekran podany kod:
Derived d = new Derived();
d.Fun(1);
  1. Base.Fun(int x)
  2. Derived.Fun(int x)
  3. Derived.Fun(object o)
  4. Wywołanie metody spowoduje wyjątek
2.
Nullable<int> nullable = new Nullable<int>();
Type t = nullable.GetType();
Jaki rodzaj wyjątku zostanie rzucony po uruchomieniu tego kodu?
  1. Żaden
  2. InvalidOperationException
  3. NullReferenceExcpetion
  4. IndexOutOfRangeException
  5. ArgumentNullException
3. Załóżmy, że mamy zdefiniowaną zmienną o nazwie variable. Kiedy poniższy kod nie spowoduje rzucenia wyjątku:
variable = null;
variable.ToString();
4. Jaki będzie wynik wykonania poniższego kodu:
string s1 = new String(new char[] { 'a' });
string s2 = new String(new char[] { 'a' });
bool result = Object.ReferenceEquals(s1, s2);
Console.Write(result);

s1 = new String(new char[0]);
s2 = new String(new char[0]);
result = Object.ReferenceEquals(s1, s2);
Console.Write(result);

s1 = "Ala ma kota";
s2 = "Ala ma" + " kota";
result = Object.ReferenceEquals(s1, s2);
Console.Write(result);

s1 = "Ala ma kota";
string temp = "ma";
s2 = "Ala " + temp  + " kota";
result = Object.ReferenceEquals(s1, s2);
Console.Write(result);
  1. True, False, True, Talse
  2. False, False, True, False
  3. False, True, True, False
  4. False, False, False, False
  5. True, True, True, False
5. Jaki będzie wynik wykonania poniższego kodu:
delegate void Printer();
...
List printers = new List();
for (int i = 0; i < 4; i++)
{
  if (i % 2 == 0)
  {
      printers.Add(delegate { Console.Write(i); });
  }
  else
  {
     int j = i;
     printers.Add(delegate { Console.Write(j); });
  }
}

foreach (Printer printer in printers)
   printer();
  1. 0, 1, 2, 3
  2. 4, 4, 4, 4
  3. 4, 1, 4, 3
  4. 3, 2, 1, 0
  5. Inny

Odpowiedzi

  1. C - Derived.Fun(object o);

    W przypadku wywołania derived.Fun(1) zostanie wywołana metoda Derived.Foo(object). Metody zdefiniowane bezpośrednio na poziomie klasy wywołującej mają zawsze priorytet nad metodami zdefiniowanymi na poziomie klasy bazowej nawet jeżeli zostały przedefiniowane w klasie pochodnej. Jest tak ponieważ przy szukaniu metody najlepiej pasującej do wywołaniu, spośród wszystkich metod kandydujących odrzucane są metody zdefiniowane poza typem najbardziej wydziedziczonym. W wyniku tego kroku ze zbioru mogą zostać usunięte metody wirtualne.

  2. C - NullReferenceException

    Uruchomienie tego kod zakończy się rzuceniem wyjątku NullReferenceException w linijce z wywołaniem GetType. Typ Nullable<T> dostarcza własną implementacja ToString oraz GetHashCode ale nie GetType. Wywołanie GetType prowadzi więc do przekształcenia wartości Nullable<int> w referencje do obiektu (opakowywanie), a ponieważ w tym przypadku Nullable.HasValue jest równe false na stos odkładany jest null.

  3. Jeśli variable jest typem Nullable<T>

  4. C - False, True – bug?, True – optymalizacja kompilatora, False

  5. C - Domknięcie - Jak to działa?

19/05/2009

Name mangling, UniqueID, ClientID oraz ID

Home

Name mangling, po polsku maglowanie nazw, to w kontekście stron wzorcowych (ang. master pages) proces podmieniania identyfikatorów kontrolek przy generowaniu strony wynikowej (ze strony wzorcowej i strony z właściwą zawartością), a celem tej operacji jest zapewnianie, że identyfikatory będą na pewno unikalne w obrębie strony. Technicznie operacja ta sprowadza się do połączenia identyfikatora kontrolki z identyfikatorem kontenera w jakiej kontrolka została umieszczona, a dokładniej jego UniqueID. Operacja ta jest potrzebna ale stwarza też problemy i prowadzi niestety do błędów. Zacznę od opisu scenariusza:
  • Utworzyłem stronę wzorcową.
  • Utworzyłem stronę z zawartością.
  • Do strony z zawartością dodałem kontrolki GridView oraz SqlDataSource.
  • Skonfigurowałem połączenie z bazą danych.
  • Uruchomiłem stronę i GridView wyświetlił dane ze wskazanej tablicy.
  • Dodałem do strony kilka pól tekstowych i przycisk.
  • Dodałem metodę obsługującą naciśnięcie przycisku:
    protected void Button_Click(object sender, EventArgs e)
    {
     SqlDataSource.Insert();
    }
    
  • W kodzie strony deklaratywnie zdefiniowałem parametry wejściowe dla zapytania wstawiającego dane do bazy:
      <asp:SqlDataSource ID="SqlDataSource" runat="server" 
        ConnectionString="<%$ ConnectionStrings:Database %>" 
        SelectCommand="SELECT [Kolumna_1], [Kolumna_2], [Kolumna_3] FROM [Tabela]"
        InsertCommand="INSERT INTO users ([Kolumna_1], [Kolumna_2], [Kolumna_3]) VALUES (@Param_1,@Param1_2,@Param_3)">
      <InsertParameters>
        <asp:FormParameter Name="Param_1" FormField="PoleTekstowe_1" />
        ...
      </InsertParameters>
      </asp:SqlDataSource>
    
    W powyższym kodzie PoleTekstowe_1 to identyfikator pola tekstowego będącego źródłem danych dla parametru Param_1.
Mając gotową stronę uruchomiłem aplikację, wypełniłem pola tekstowe, nacisnąłem przycisk i otrzymałem wyjątek z komunikatem: Cannot insert the value NULL into column 'Kolumna_1'. Sprawdzam czy nie zrobiłem jakiejś literówki ale wszystko wygląda prawidłowo. Chwila zastanowienia i przypominam sobie, że przecież używałem już podobnego kodu i nie miałem problemów. Główkują dalej i zaczynam patrzeć podejrzliwie na użycie stron wzorcowych. Przenoszę więc cały kod do 'zwykłej' strony, odpalam i wszystko działa. W tym momencie przypominam sobie o Name mangling ale przecież nie zrezygnuję z tego powodu ze wszystkiego co dają strony tego typu.

Żeby nie przeciągać problem rozwiązałem rezygnując z deklaratywnego definiowana parametrów dla źródła danych na poziomie strony na rzecz zdefiniowania ich w kodzie. Oczywiście to nie wszystko. Sztuczka polega na tym, żeby przy podawaniu identyfikatora kontrolki, która będzie źródłem danych dla parametru należy użyć właściwości Control.UniqueID:
protected void Page_Load(object sender, EventArgs e)
{
   this.SqlDataSource.InsertParameters.Add(new FormParameter("Param_1", this.PoleTekstowe_1.UniqueID));
   this.SqlDataSource.InsertParameters.Add(new FormParameter("Param_2", this.PoleTekstowe_2.UniqueID));
   this.SqlDataSource.InsertParameters.Add(new FormParameter("Param_3", this.PoleTekstowe_3.UniqueID));
}
Control.UniqueID zawiera już przemaglowany identyfikator (globalnie unikalny w obrębie strony) i w związku z tym źródło danych nie ma problemu ze znalezieniem odpowiedniej kontrolki.

Dociekliwi mogą powiedzieć, że jest jeszcze właściwość Control.ClientID. Od razu mówię, że jeśli użyjemy tej właściwości to błąd również wystąpi. Na pierwszy rzut oka może wydawać się to dziwne ponieważ ClientID zawiera już zmieniony identyfikator. Różnica polega na tym, że do oddzielenie poszczególnych składowych identyfikatora ClientID używany jest inny separator niż przy UniqueID. Poniżej przykład dwóch identyfikatorów dla tej samej kontrolki:

UniqueID: ctl00$ContentPlaceHolder1$Nazwa
ClientID: ctl00_ContentPlaceHolder1_Nazwa

Dlaczego jednak użyto innych separatorów. Zacznijmy od tego, że aby proces podmieniania nazw był odwracalny ASP.NET musi umieć wydzielić z wygenerowanego identyfikatora jego składowe czyli identyfikatory kolejnych kontenerów i na końcu kontrolki. To jest przyczyna, dla której wprowadzono separatory. Separatorem powinien być oczywiście znak, który nie może wystąpić w bazowym identyfikatorze, w tym przypadku '$' ale można to zmienić.

Tutaj dochodzimy do momentu, w którym potrzebujemy z jakiegoś powodu odwołać się do wyrenderowanej kontrolki z poziomu JavaScript'u. Oczywiście potrzebujemy jakiś identyfikator i tu pojawiaja się problem. Otóż co zrobić jeśli jako separator użytu znaku niedozwolonego w identyfikatorach przez JavaScript? Problem ten postanowiono obejść wprowadzając ClientID i używając innego dozwolonego separatora czyli podkreślnika '_'. Reasumując UniqueID (renderuje się do atrybutu name tagu HTML) identyfikuje globalnie kontrolkę na potrzeby silnika ASP.NET, a ClientID (renderuje się do atrybutu id tagu HTML) na potrzeby JavaScript'u

11/05/2009

Visual Studio 2010

Home

Kilka dni temu, 6 maja, byłem na forum architektów, na którym między innymi pokazano wersję pre-beta Visual Studio 2010. Mówiono również o innych rzeczach ale część dotycząca nowej wersji tego środowiska była, przynajmniej dla mnie, zdecydowanie najciekawsza. Być może ta zasługa, jak zawsze rewelacyjnego Tomasza Kopacza ale nowe funkcjonalności wyglądały naprawdę bardzo obiecująco. Pozwolę sobie wymienić niektóre z nich:
  • UI oparte w całości o WPF
  • Dodano nowy sposób wyszukiwania w IntelliSense. W obecnej chwili po wpisaniu na przykład frazy Add IntelliSense poda wszystkie typy, metody itd. zaczynające się od tej frazy. W VS 2010 poda wszystkie typy, metody itd. zawierające podaną frazę czyli Add, AddTo ale również TestAdd, SuperAddTo.
  • Podgląd hierarchii wywołań pokazujący co wywołuje dana metoda i dalej co wywołują te metody itd.
  • Dynamiczne generowane diagramy sekwencji na podstawie kodu.
  • Generowanie szablonów klas. Na przykład piszemy:

    MyClass c = new MyClass();
    
    Załóżmy, że MyClass nie istnieje. W VS 2010 będziemy mogli wygenerować szablon dla tej klas. Podobny mechanizm będzie dostępny dla metod.
  • Transformacje konfiguracji. Tworzymy szablon konfiguracji i definiujemy przy pomocy specjalnego języka jak ten szablon ma zostać podczas kompilacji przekształcony w zależności od jakichś parametrów.
  • Dużo narzędzi do modelowania: integracja z Visio, powiązanie modelu z kodem na przykład po to aby kontrolować jak przebiegają prace - jakie komponenty zostały już zaimplementowane. Dalej kontrola poprawności kodu na podstawie modelu np.: czy nie ma zależności pomiędzy warstwami prezentacji i danych.
  • Lepsze narzędzia do pracy z aplikacjami wielowątkowymi. Profiler pokazujący jak działa nasza aplikacja wielowątkowa, który wątek generuje największe obciążenie, jak rozkłada się obciążenie na rdzenie/procesory. Narzędzia do radzenia sobie z zakleszczeniami.
  • Impact Analysis - Powiązanie testów z metodami, których dotyczą. Po modyfikacji metody wiemy, które testy należy powtórzyć.
  • Narywanie testów UI aplikacji okienkowych (WPF, Win Forms) - narzędzie oparte o Microsoft UI Automation. Testy takie można było pisać do tej pory tylko ręcznie.
Na koniec zostawiłem sobie dwa hity:

Historyczny debugger!!! Podczas debugowania będzie można podejrzeć jak zmieniał się stan aplikacji w różnych chwilach czasowych.

Nagrywanie stanu aplikacji!!! Przy pomocy odpowiedniego narzędzia będzie można zrzucić stan aplikacji (u klienta) do pliku i potem wczytać go do VS 2010 i rozpocząć debugowanie (potrzebny .NET 4.0).

Zaznaczam, że nie wiem jak te i inne rzeczy będą finalnie zapakowane w pudełka i sprzedawane. Z ciekawostek dodam, że Microsoft używa VS 2010 od początki roku 2008, a prace nad VS 2012 trwają już od roku :). Niby to nic dziwnego bo jak lepiej przetestować środowisko niż korzystając z niego, a prace nad tak złożonym programem muszą trwać. Ale jak sobie pomyślę, że ludzie z Microsoftu pracują teraz z/nad zabawkami, które poznam za parę lat, a zacznę z nimi pracować pewnie jeszcze później to co tu dużo mówić, ja też tak chcę!!!.

07/05/2009

"Autentykacja" - Czy takie słowo istnieje?

Home

Nie jestem purystą językowym ale niektóre rzeczy mi przeszkadzają i uważam, że powinno się mówić, a szczególnie pisać po polsku, a nie po "polskiemu". Właśnie jestem po lekturze kilku dokumentów dotyczących bezpieczeństwa, zarządzania użytkownikami oraz uprawnieniami, w których nagminnie używano słowa autentykacja. Niby wiadomo, że chodzi o proces uwierzytelniania (ang. authentication) ale takiego słowa nie ma po prostu w języku polskim. Jeśli ktoś nie wierzy to zapraszam do słownika języka polskiego, dostępny również on-line. Pół biedy kiedy używamy tego sformułowania w luźnej rozmowie ale słowo pisane jest bardziej wrażliwe na takie neologizmy, dziwne sformułowania itd.

06/05/2009

Dopowiedzenie

Home

Mam niewielką uwagę w kontekście postów Czemu zdarzenia nie działają??? oraz Czemu zdarzenia nie działają??? (część 2). Główne ich przesłanie:

W przypadku dynamicznego tworzenia kontrolek, czy to bezpośrednio czy to przy okazji użycia kontrolek data bound zawsze należy pamiętać aby dynamiczne kontrolki były tworzone nie tylko przy inicjalnym odwołaniu do strony ale również przy kolejnych.

Jest cały czas aktualne przy czym chciałbym dodać jedną rzecz. Otóż zapomniałem napisać, że w przypadku dowiązywania kontrolek do źródeł danych (na przykład do ObjectDataSource) nie trzeba się tym przejmować. Jest tak ponieważ źródła danych same dbają o to aby kontrolka została zasilona danymi w odpowiednim momencie. W szczególności nie trzeba jawnie wołać metody DataBind.

05/05/2009

Jak rozpoczęła się moja przygoda z Delphi

Home

Los chciał, że od jakiegoś czasu do moich obowiązków dołączyła potrzeba utrzymywania i pielęgnacji komponentu drzewo-listowego (kontrolka pozwalająca na wyświetlanie danych hierarchicznych i listowych z wieloma kolumnami) napisanego w Delphi. Długi broniłem i zapierałem się nogami ale w końcu musiałem się poddać. Dodam, że moja wiedza na temat Delphi była mniej niż skromna. Była bo teraz jest po prostu skromna :).

Plusem całej sytuacji jest to, że kod, który otrzymałem do pielęgnacji został dobrze wytestowany, komponent zachowuje się bardzo stabilnie, jednym słowem zgłoszeń błędów jest mało.

Można zapytań po co utrzymywać stary komponent skoro można napisać nowy albo jeszcze lepiej wybrać i zakupić jeden z istniejących na rynku. Z przepisaniem problem jest taki, że potrzeba by pewnie jednego osobo-roku zdolnego programisty aby to przepisać na .NET (tyle mniej więcej zajęło napisanie starego komponentu) plus dużo czasu na wytestowanie i stabilizację. Ameryki nie odkryję jeśli powiem, że znalezienie takiej ilości zasobów na w gruncie rzeczy niekomercyjny projekt to trudna sprawa.

Jeśli chodzi o zakupienie komercyjnego rozwiązania to pomysł dobry ale zawsze jest jakieś ale. Osobiście zapoznałem się i przebadałem pod względem wydajności, funkcjonalności itd. około dziesięciu rozwiązań dostępnych na rynku. Niektóre komponenty mają naprawdę duże możliwości. Problem polega na tym, że pomimo wszystko nie pokrywają wszystkich cech komponentu stworzonego na wewnętrzne potrzeby firmy, w której pracuję. Nie wchodząc w szczegóły firmowy produkt potrafi naprawdę dużo. Sytuacja bez wyjścia? Tak i nie. Tak, ponieważ z pewnością nie uda się znaleźć rozwiązania w 100% pokrywającego wymagania. Nie, ponieważ w obecnej chwili firma rozwija swoje produkty w .NET, a używanie starego komponentu wymaga użycia ActiveX z czego raz po raz wynikają jakieś problemy. Nie wspominając już o konieczności utrzymywania starego kodu.

Reasumując moja przygoda z Delphi jeszcze potrwa i mam nadzieję, że się czegoś nauczę. Na razie dzięki porównaniu IDE dla Delphi z Visual Studio zdałem sobie sprawę jak duży postęp dokonał się jeśli chodzi o zintegrowane środowiska programistyczne. Z drugiej strony porównanie czasu kompilacji dużego projektu napisanego w Delphi do projektu napisanego w .NET wypada bardzo znacząco na korzyść tej pierwszej technologii. Niestety podobnie jest z szybkością uruchamiania się aplikacji.

04/05/2009

Skrzydła furii

Home

Wiele osób, w tym i ja, spędziło długie godziny swoich młodzieńczych lat grając w takie gry jak Wings of fury. Kilka kolorów na krzyż, idea prosta ja budowa cepa, a grywalności tyle, że można by podzielić pomiędzy kilkadziesiąt współczesnych tytułów i jeszcze by zostało. Kiedy, więc przed weekendem majowym natknąłem się na sequel tej gry stworzony przez polskich programistów - Wings of fury 2 moje serce przyspieszyło.

Na szczęście, po za poprawioną grafiką, dużo się nie zmieniło. Znowu latamy w lewo i w prawo, zrzucam bomby, odpalamy rakiety, startujemy i lądujemy na lotniskowcu itd. Jednym słowem to co tygryski lubią najbardziej.

Jest trochę bugów, które utrudniają grę (problemy ze sterowaniem samolotu, menu gry słabo działa) ale da się z nimi grać. Najgorsze, że pomimo niewielkich wymagań występują problemy z uruchomieniem gry. W moim przypadku gra nie chciała działać na dwóch z trzech testowanych komputerów.

Tytuł jest w pełni darmowy i co dla mnie najciekawsze został napisany w C# na silniku MOGRE. Jest to zarządzana nakładka na silnik OGRE, a jego możliwości wydają się bardzo duże. Zaciekawiło mnie to tyle, że przymierzam się do zrobienia czegoś przy jego pomocy. Co z tego wyjdzie czas pokaże.

Jako podsumowanie mogę powiedzieć, pomimo pewnych zastrzeżeń, brawo dla autorów.

28/04/2009

Słów kilka o terminologii

Home

Ostatnio odniosłem wrażenie, że jest spora grupa osób, która dość niechlujnie używa terminów szyfrowanie (deszyfrowanie) i kodowanie (dekodowanie). Prowadzić to może natomiast do niezrozumienia u rozmówcy lub czytelnika.

Kodowanie to proces przekształcenie danych wejściowych do innej, bardziej pożądanej i użytecznej w danym kontekście postaci. W jednym przypadku użyteczne będą to dane skompresowane, w innym zapisane przy użyciu odmiennego alfabetu, a w jeszcze innym zaszyfrowane. Zgadza się, szyfrowanie jest specyficzną formą kodowania. Podstawowa różnica pomiędzy szyfrowaniem, a innymi formami kodowania polega natomiast na tym, że szyfrowanie wymaga podania klucza. Zarówno w przypadku innych form kodowania jak i szyfrowania algorytm może być dobrze znany i dostępny publicznie. Tylko, że nie znając klucza, a znając algorytm szyfrujący nie zdziałamy dużo. Pomijam tutaj sytuację kiedy algorytm został skompromitowany (złamany) lub kiedy użyty klucz jest zbyt słaby, łatwy do odgadnięcia.

Pojęcie kodowania jest, więc bardzo ogólne. Podsumowując mówiąc - te dane są zakodowane - tak naprawdę nie przekazujemy żadnej informacji, a już na pewno nie o tym, że dane zostały zaszyfrowane - utajnione.

27/04/2009

"Kobieta pierwotna"

Home

"Kobieta pierwotna" to tytuł naprawdę świetnej komedii, która miałem przyjemność zobaczyć wczorajszego wieczoru w teatrze "Bajka". W roli tytułowej występuje Hanna Śleszyńska, a przedstawienie zostało wyreżyserowane przez Arkadiusza Jakubika. Innych aktorów nie wymieniam bo ich nie było. Na scenie można jeszcze było zobaczyć Pana X czyli szmacianą kukłę reprezentującą mężczyznę :) Ci z was, którzy w tym momencie zaczynają podejrzewać o czym jest ta sztuka zapewne mają rację. W kilku słowach: o stosunkach damsko-męskich i ogólnie międzyludzkich, o poszukiwaniu mężczyzny, z którym kobieta mogłaby spędzić całe życie, o naszych (czytaj mężczyzn) wadach.

Nie trudno domyśleć się również, że przedstawienie miało postać monologu. Ale jakiego monologu, w czasie półtorej godziny więcej śmiałem się niż nie! Dowcipy, kąśliwe i ironiczne uwagi pod kątem mężczyzn wywoływały salwy śmiechu na widowni i bynajmniej nie tylko damskiej części publiczności. Hanna Śleszyńska była po prostu przezabawna, potrafiła improwizować, bardzo szybko nawiązała kontaktu z publicznością. W pewnym momencie aktorka zeszła nawet ze sceny i rozpoczęła dowcipny dialog z jednym z widzów (dowcipny choć trochę sprośny, więc nie będę cytował).

Podsumowując lekka i przyjemna rozrywka najwyższych lotów skłaniająca jednak do refleksji plus świetna, wręcz genialna rola Hanny Śleszyńskiej. Jednym słowem z czystym sumieniem polecam. Na zakończenie dodam jeszcze pesymistyczną uwagę, że kultura w naszym społeczeństwie najwyraźniej ginie skoro ludzie nie potrafią przyjść punktualnie do teatru, przeszkadzając sobie, a przede wszystkim innym.

23/04/2009

Steve Ballmer - spotkanie ze społecznością

Home

Dzisiejszego przedpołudnia udałem się na spotkanie z prezesem Microsoftu chociaż mówiąc szczerze miałem mieszane uczucia co do niego. Czemu? Po prostu nie chciałem tracić czasu na zobaczenie może śmiesznego ale medialnego przedstawienia, z których Steve Ballmer słynie. Z perspektywy mogę jednak w pełni szczerze powiedzieć, że było warto.

Spotkanie było co prawda krótkie, ale na dobrego showmana zawsze dobrze popatrzeć. I nie chodzi bynajmniej o to, że Steve Ballmer zrobił coś dziwnego czy bardzo ekscentrycznego. Wchodząc do sali miał na szyi biało-czerwony szalik z napisem Polska co wywołało gromkie brawa. Przemawiając zachowywał się na luzie, mówił konkretnie i zwięźle. Nawiązując do obecnej sytuacji ekonomicznej, czytaj kryzysu, powiedział że co prawda rynek się skurczył ale wbrew pozorom jest to doskonały moment na wprowadzanie nowych produktów i innowacji. Krótko mówiąc z IT jest całkiem dobrze :) Zachęcał do wysyłania do niego wiadomości z sugestiami dotyczącymi produktów Microsoftu. Na pytania społeczności odpowiadał w sposób błyskotliwy, żartował (chociaż mógł się do nich zapewne przygotować ponieważ były autoryzowane).

Tak czy inaczej występy publiczne to z pewnością jego żywioł i jest w tym naprawdę dobry. A mistrza przy pracy zawsze ogląda się z przyjemnością bez względu na czym ta praca polega.

17/04/2009

Bug w Visual Studio?

Home

W poście pisałem o bardzo ciekawej i użytecznej funkcjonalności Visual Studio umożliwiającej testowanie kodu bezpośrednio z poziomu środowiska bez potrzeby pisania programów testowych. Dzisiaj w pracy chciałem skorzystać z tej możliwości, a dokładniej chciałem z poziomu okna Class View utworzyć obiekt i umieścić go w oknie Object Test Bench. Służy do tego polecenie menu kontekstowego Create Insance. Wybrałem konstruktor domyślny i ku mojemu zdziwieniu otrzymałem okienko błędu, którego nigdy wcześniej nie widziałem, z mniej więcej takim komunikatem:

The type name 'ClassName' does not exist in the type 'Test.Test'

Po bliższym przyjrzeniu okazało się, że błąd spowodowany jest tym, że klasa obiektu, który chciałem utworzyć znajduje sie w przestrzeni nazw, która zawiera inną klasę o takiej samej nazwie jak nazwa przestrzeni np.:

namespace Test
{
  public class Test
  {
  }
}
Podobny błąd występuje również przy próbie wywołania metody statycznej z poziomu okna Class View (polecenie Invoke Static Method). Aby pozbyć się problemy wystarczy zmienić nazwę przestrzeni lub klasy. Co ciekawe wygląda na to, że występowanie problemu związane jest typem projektu. Ja zauważyłem go dla projektu konsolowego. Dla projektu typu Class Library już nie. Na sieci znalazłem informacje, że taki komunikat może pojawiać się również w innych scenariuszach dla projektów innego rodzaju. Dodam jeszcze, że błąd zaobserwowałem w Visual Studio 2008.

11/04/2009

Życzenia

Home



Życzę wszystkim wesołych i spokojnych Świąt Wielkiej Nocy spędzonych z najbliższymi.

Serdecznie pozdrawiam
Michał Komorowski

09/04/2009

Visual Studio i za długie ścieżki

Home

Możliwe, że to znany problem ale ja natknąłem się na niego ostatnio. Otóż siedzę sobie i koduję, oczywiście używając Visual Studio i w pewnym momencie zdecydowałem, że potrzebuję z bazy kodów (aktualnie używam Visual Source Safe) pobrać nowy projekt. Sprawa prosta, wybrałem polecenie Add -> Existing Project..., wyszukałem interesujący mnie projekt, wybrałem Ok i... Visual Studio kaput. Bez żadnego ostrzeżenia, komunikatu środowisko zostało zamknięte. No cóż, myślę sobie, spróbuję jeszcze raz. Za drugim, trzecim razem dokładnie to samo. Sprawa dziwna bo inne projekty pobierają się bez problemu. Zaczynam się zastanawiać i nagle przebłysk, że chyba słyszałem o czymś takim, o jakimś problemie z długością ścieżek. Kilka eksperymentów i okazało się, że problem występuje jeżeli pełna ścieżka do pliku z projektem umieszczonym w bazie kodów przekracza 120 znaków. Jak sobie z tym poradzić. Nie pozostaje nic innego jak pobrać projekt na dysk przy pomocy Visual Source Safe Explorer i wczytać go z tej lokalizacji. Dalej Visual Studio już sobie poradzi.

Velocity - co nowego?

Home

Dwa dni tempu pojawiła się wersja CTP 3 Velocity. Ponieważ w kilku postach wspominałem o tej technologii pozwalającej na rozproszone cache'owanie danych chciałbym w kilku słowach napisać o najważniejszych nowościach względem CTP 2.
  • Mechanizm powiadamiania - W tej chwili możliwe jest reagowanie kiedy jakiś region (grupa logicznie powiązanych z sobą obiektów umieszczonych w cache'u) jest dodawany, aktualizowany lub usuwany z cache'a. Mechanizm powiadamiania umożliwia również automatyczne unieważnianie cache lokalnego. Cache lokalny utrzymywany jest po stronie klienta i zawiera kopie danych pobranych z klastra. Do tej pory dane zgromadzone w tym cache'u były unieważniane dopiero po upływie określonego czasu. W tej chwili zostaną unieważnione tak szybko jak cache lokalny otrzyma powiadomienie.
  • Poprawienie wydajności.
  • Poprawienie bezpieczeństwa - Usługa gospodarza hosta działa teraz z uprawnieniami konta (mniejszymi niż dotychczas): Network Service.
  • Nowe opcje zarządzania klastrem - Wraz z CTP 3 rolę zarządcy klastra może pełnić instancja SQL Server'a. Wcześniej za zarządzanie klastrem byli odpowiedzialni tylko tzw. główni gospodarze (ang. lead host). Równocześnie usunięto możliwość przechowywania konfiguracji klastra w pliku XML.
  • Ulepszenia programu instalacyjnego - Wprowadzono możliwość automatycznej instalacji oraz zwiększono zakres dostępnych w czasie instalacji opcji.
  • Nowe API - Ta zmiana nie cieszy mnie za bardzo ale nic nie poradzę. Ogólnie zmieniło się nazewnictwo klas, delegatów, kolejność parametrów w metodach itd. czyli w sumie całkiem sporo.

30/03/2009

.NET Remoting - podsumowanie

Home

W ostatnim czasie kilkukrotnie rozmawiałem, odpowiadałem na pytanie dotyczące przekazywania danych z serwera do klienta i w drugą stronę przy użyciu .NET Remoting. W poście tym chciałbym zebrać wszystkie informacje i przedstawić w spotaci klarownej tabelki. Dalej będę używał skrótu MBVO (ang. Marshal by value object) dla określenia obiektów klas przekazywanych przez wartość oraz MBRO (ang Marshal by reference object) dla określenia obiektów klas przekazywanych przez referencję. Klasy obiektów MBVO oznaczone są atrybutem SerializableAttribute, a klasy obiektów MBRO dziedziczą po MarshalByRefObject i ewentualnie mogą być oznaczone atrybutem SerializableAttribute ale jest to zbyteczne.



Metody obiektów MBVO wykonują się zawsze lokalnie dla wywołującego. Metody obiektów MBRO wywołują się albo lokalnie albo zdalnie dla wywołującego w zależności od tego czy pracuje od z oryginalnym obiektem czy z proxy do niego.

W przypadku obiektów MBVO zarówno klient jak i serwer muszą znać pełną definicję klasy obiektu. Serwer po to aby utworzyć obiekt i przekazać go do klienta, a klient aby odtworzyć go po swojej stronie. W przypadku obiektów MBRO nie jest to konieczne ponieważ obiekty te szeregowane są przez proxy. Aby utworzyć proxy wystarczy znajomość interfejsu. Dokładną definicję klasy musi więc znać tylko strona udostępniająca obiekt tej klasy.

25/03/2009

Synchronizacja - wydajność

Home

Niedawno opublikowałem post dotyczący prymitywów synchronizacyjnych. Przy tej okazji zostałem zapytany czemu użycie prymitywów takich jak semafor czyli implementowanych po stronie systemu operacyjnego jest wolniejsze. Odpowiedź zamieściłem w odpowiedzi do komentarz. W tym poście chciałem przedstawić jeszcze wyniki krótkiego testu jaki przeprowadziłem. Dotyczył on zbadaniu wydajności rozpatrywanych wcześniej prymitywów i polegał na wielokrotnym wchodzeniu i wychodzeniu do/z sekcji krytycznej przez dwa watki. Sekcja krytyczna była oczywiście zabezpieczonej przez te prymitywy. Do pomiaru czasu użyłem klasy Stopwatch. Wyniki są zgodne z tym co napisałem. Zamieściłem je poniżej.


20/03/2009

Inkwizycja

Home

Ostatnio po dłuższej przerwie sięgnąłem ponownie to opowiadań Jacka Piekary o Inkwizytorze Jego Ekscelencji Biskupa Hez-hezronu Mordimerze Madderdinie i pomimo, że znam już ich fabułę naprawdę nie mogłem się oderwać. Nietuzinkowy bohater, akcja, świetny warsztat pisarski, ciekawy i oryginalny świat, wciągająca fabuł i do tego nutka skandalu. Pozwolę sobie przytoczyć fragment opisu książki: "Najbardziej wstrząsająca i bluźniercza wizja w historii polskiej fantastyki!" Czego trzeba więcej? Naprawdę polecam ten cykl, na który składają sie cztery tomy opowiadań: "Sługa Boży", "Młot na czarownice", "Miecz aniołów", "Łowcy Dusz" oraz pierwszy tom dłuższej serii "Płomień i krzyż".

18/03/2009

Velocity (cz. 2)

Home

Oj długo zbierałem się do napisania tego posta chociaż po drodze pojawiło się kilka innych. Nadeszła jednak pora aby pojawił się, zresztą obiecany, post dotyczący terminologii związanej z Velocity, procesu konfigurowania i instalowania (pierwszy wpis dotyczący tej technologii znajdziemy tutaj). Dla tych, którzy zapomnieli lub nie czytali, Velocity to rozproszony cache - rozproszona pamięć podręczna. Celem Velocity, jest zwiększyć niezawodność i wydajność działania aplikacji poprzez replikację, rozproszenie oraz zapewnienie szybkiego dostępu do danych. Produkt rozwijany jest przez Microsoft i finalna jego wersja nie ujrzała jeszcze światła dziennego. Obecnie dostępna wersja to tzw. CTP2 (ang. Community Technology Preview). W poście tym nie będę opisywał wszystkiego, wgłębiał się w szczegóły bo od tego jest dokumentacja, zresztą bardzo dobra. Chciałbym napisać tylko o najważniejszych moim zdaniem elementach i skłonić do przyjrzenia się Velocity bliżej.

Przy omawianiu architektury Velocity posłużę się dwoma obrazkami, które zaczerpnąłem z dokumentu stanowiącego wprowadzenie do technologii. Znajdziemy go tutaj.



Velocity uruchamiane jest w formie kolekcji usług systemowych. W szczególności może to być pojedyncza usługa zainstalowana na jednej maszynie. Usługę taką nazywamy usługą gospodarza cache'a (ang. cache host service), a maszynę na której jest zainstalowana serwerem cache'a (ang. cache server). Zbiór takich usług nazywamy natomiast klastrem (ang. cache cluster). Aby wszystkie składowe klastra mogły się z sobą dogadać muszą mieć dostęp do wspólnej konfiguracji. Może ona zostać zapisana w bazie danych lub w pliku udostępnionym w współdzielonym katalogu. Co ważne wiele czynności o naturze administracyjnej czy też konfiguracyjnej można wykonać bez dotykania się plików konfiguracyjnych przy pomocy konsoli PowerShell. Przy instalacji Velocity rejestruje swoje własne komendy.

Monitorowaniem i zarządzaniem całego klastra zajmują się wyznaczone jego węzły tzw. główni gospodarze (ang. lead host). To ich zadaniem jest zapewnić aby klaster był cały czas dostępny, monitorowanie dostępności poszczególnych węzłów klastra czy też zapewnienie żeby dane były rozłożone równomiernie pomiędzy jego węzły. Ilość głównych gospodarzy zależy od wielkości klastra.



W obrębie klastra możemy utworzyć jeden lub więcej tzw. nazwanych cache'y (ang. named cache) lub ewentualnie skorzystać z domyślnego. Nazwany cache'y to po prostu wydzielony fragment rozproszonej pamięci, w którym można umieszczać dane. Często zamiast używać terminu nazwany cache mówi sie po prostu cache.

Można zapytać czemu warto korzystać z tej opcji skoro udostępniono cache domyślny. Otóż każdy cache nazwany jest konfigurowany oddzielnie. Konfigurowanie dotyczy natomiast tak ważnych rzecz jak to jak długo dane będą przechowywane w cache'u czy też typu cache. Dla przypomnienia, typ replikujący po umieszczeniu w nim porcji danych tworzy jej kopię i umieszcza na węzłach klastra (dobre dla danych wolno-zmiennych). Typ partycjonujący rozmieszcza dane równomiernie pomiędzy poszczególne węzły klastra (dobry dla danych szybko-zmiennych). Typ replikujący się nie jest dostępny w CTP2.

W cache'u możemy umieścić każdy serializowalny obiekt (dalej będę stosował wymiennie pojęcia obiekt/dane). Umieszczając obiekt w cache podajemy klucz, którego użyjemy następnie do jego odczytania. Istnieje również możliwość grupowania obiektów w obrębie cache'a. W nomenklaturze Velocity taką grupę obiektów nazywamy regionem (ang. region). Umożliwiają one wspólne zarządzanie większą grupą obiektów. Użycie regionów ma jednak tą wadę, że region jest przypisany do jednego gospodarza. W przypadku pojedynczych obiektów cache może je natomiast równomiernie rozłożyć pomiędzy węzły klastra. Jako podsumowanie można przyrównać nazwany cache do bazy danych. Regiony natomiast do tabel w tejże bazie.

Ale co w tym takiego specjalnego? Po pierwsze Velocity zapewnia, że jeśli umieścimy w cache'u jakieś dane to będą one dostępne z każdej maszyny, na której jest on widoczny. Nawet jeśli dane będą znajdować się fizycznie gdzie indziej, w sposób przezroczysty dla klienta zostaną odnalezione i przesłane przez sieć. Ale to nie wszystko. Velocity zapewnia również całkowicie przezroczystą dla klienta replikację danych. Możemy określić w ilu kopiach ma być przechowywana każda porcja umieszczonych w cache'u danych. Spośród wszystkich replik wyróżnia się replikę główną, na której realizowane są wszystkie aktualizacje i odczyty oraz repliki poboczne, o charakterze zapasowym.

Synchronizacja tych kopii odbywa się w sposób automatyczny i niewidoczny dla klienta! Po drugie w przypadku awarii, któregoś z węzłów z klastra i utracie dostępu do kopii głównych ich rolę przejmują kopie poboczne. Oczywiście nie można przesadzić z ilością kopii. Ich synchronizacja przecież kosztuje.

Instalacja Velocity nie jest kłopotliwa i zajmuje kilkanaście minut. W gruncie rzeczy sprowadza się do uruchomienia instalatora. W obecnej wersji automatyczna instalacja umożliwia zainstalowanie tylko jednej instancji usługi na jednej maszynie. Jeśli chodzi o konfigurowanie to wspomnę jeszcze tylko, że: konfiguracja usługi znajduje się w pliku .config w katalogu instalacyjnym Velocity na danej maszynie. Konfigurację klienta Velocity umieszczamy natomiast w pliku .config tyle, że danej aplikacji webowej lub okienkowej. Schemat konfiguracji nie jest trudny i jest dokładnie opisany w dokumentacji.

16/03/2009

TechFest 2009

Home

Możliwość uczestnictwa w konferencji TechFest 2009 bardzo mnie ucieszyła. Wiele sesji, zagraniczni uczestnicy, laboratoria no i miejsce – Hotel Gołębiewski z Wiśle. Niestety w obecnej chwili jestem daleki od zadowolenia. Czemu? Płatność zrealizowałem już ponad miesiąc temu, a od tego czasu nie dostałem potwierdzenia. Zestawu powitalnego ani widu ani słychu choć do konferencji pozostały jeszcze tylko niecałe trzy tygodnie! Kontakt z organizatorami, jaki kontakt? Brak odpowiedzi na telefony i wiadomości e-mail (no dobrze raz udało mi się dodzwonić). Dla mnie to brak kultury i dobrego wychowania oraz zwykłe lekceważenie. Mam wrażenie, że organizatorzy imprezy jeszcze długo nie będą mogli pozbyć się swoistego smrodku na jaki udało im się zapracować. Jest jeszcze szansa, że sama konferencja przyćmi dotychczasowe niepowiedzenia. Po dotychczasowych doświadczeniach jednak szczerze w to wątpię, obym się mylił.

08/03/2009

Prymitywy synchronizacyjne - jak dobrze je znamy?

Home

Ostatnio zadałem kilku osobom następujące pytanie. Spośród wymienionych poniżej prymitywów synchronizacyjnych wybierz, te które mogą zostać użyte do synchornizacji między-procesowej:
  • Klasa Monitor
  • Klasa Mutex
  • Klasa Semaphore
  • Słowo kluczowe lock
  • Klasa AutoResetEvent
  • Klasa ManualResetEvent
Sądziłem, że pytanie należy raczej do tych z kategorii łatwiejszych. Odpowiedź na nie przysporzyła jednak niestety sporo kłopotów. Na poniższej liście zostawiłem tylko elementy stanowiące poprawną odpowiedź:
  • Klasa Mutex
  • Klasa Semaphore
  • Klasa AutoResetEvent
  • Klasa ManualResetEvent
Czym różnią się wybrane prymitywy synchronizacyjne od pozostałych? Moim zdaniem można wymienić cztery zasadnicze różnice:
  • Po pierwsze klasy te dziedziczą pośrednio po klasie MarshalByRefObject i w związku z tym instancje tych klas mogą przekraczać granice po między dziedzinami aplikacyjnymi (ang. application domain).
  • Po drugie klasy te są wywiedzione z klasy WaitHandle i w związku można ich używać w podobny sposób chociaż mają zupełnie inną semantykę. W szczególności można sobie wyobrazić scenariusz, w którym wątek oczekuje równocześnie na zwolnienie semafora, mutexa i zapalenie się sygnału/zdarzenia (należy użyć metody statycznej WaitHandle.WaitAll())
  • Po trzecie klasy te stanowią w rzeczywistości opakowanie na natywne/systemowe prymitywy synchronizacyjne udostępnione w systemie operacyjny co generalnie powoduje, że są wolniejsze.
  • Po czwarte i najważniejsze z perspektywy pytania te cztery prymitywy synchronizacyjne mogą zostać nazwane. Inaczej mówiąc, w jednym procesie możemy utworzyć semafor o nazwie "MY SEMAPHORE", a w drugim procesie uzyskać dostęp do tego samego semafora posługując się podaną nazwą. W tym celu można użyć metody statycznej Semaphore.OpenExisting() lub z odpowiedniego konstruktora.
Moim zdaniem wiedza o sposobie działania, semantyce poszczególnych prymitywów synchronizacyjnych jest niezwykle ważna dla każdego programisty bez względu z jaką technologią pracuje. Błędy związane z synchronizacją są jednymi z najtrudniejszych do wykrycia i naprawienia, a wiele z nich można by unikać gdyby wszyscy programiści znali dokładnie narzędzie swojej pracy.

05/03/2009

Xceed Chart

Home

Ostatnie kilka dni w pracy spędziłem na oprogramowywaniu komponentu do rysowania wykresów firmy Xceed w wersji dla Windows Forms (jest też wersja dla ASP.NET). Wcześniej nie korzystałem z produktów tej firmy i po tych kilku dniach kontaktu z jednym z nim muszę przyznać, że pracowało mi się bardzo przyjemnie.

API nie jest bardzo trudne, a jeśli sprawia trudności zawsze można sięgnąć do bogatej biblioteki interaktywnych przykładów. Komponent wręcz przytłacza swoimi możliwościami. Mam na myśli naprawdę przeogromną liczbę opcji konfiguracyjnych. Wybierać możemy pomiędzy wykresami dwu- i trój- wymiarowymi, liniowymi, słupkowymi, radarowymi... i prawie dowolnie dostosowywać ich wygląd do swoich potrzeb. Podczas pracy komponent zachowywał się bardzo stabilnie. Jak do tej pory nie zauważyłem również problemów z wydajnością. Nadmienię jeszcze, że pracowałem z starszą wersją komponentu (4.0). Zakładam, że wersja najnowsza (4.2) musi być co najmniej tak dobra jak stara.

Jeśli ktoś potrzebuje kontrolki do rysowania wykresów to naprawdę polecam. Największy mankament to niestety cena - kilkaset dolarów. Do użytku osobistego drogo ale dla firmy nie jest to już duży wydatek.

22/02/2009

Zakamarki Visual Studio (cz. 4)

Home

W czwartym już poście dotyczącym różnych ciekawych funkcji środowiska programistycznego Visual Studio chciałbym przyjrzeć się dokładniej oknu Call Stack, a przy okazji poruszyć kilka innych tematów.

Okno Call Stack, breakpoint'y...

Czym jest breakpoint zapewne każdy programista wie, a jeśli nie to wstyd. Z drugiej strony nie jestem już taki pewny czy każdy programista zna wszystkie możliwości breakpoint'ów w Visual Studio: filtrowanie, warunki, zliczanie, uruchamiania makr czy wstawienie breakpoint'a do metody, której kodu nie znamy (o tym pisałem tutaj). Opisanie tych rzeczy to temat na oddzielny post. W tym miejscu skupmy się na użyciu pułapek w oknie Call Stack.

Po pierwsze umożliwia ono wstawianie breakpoint'ów. W tym celu wybieramy interesującą nas ramkę (metodę), zaznaczamy ją i naciskamy przycisk F9 (albo wywołujemy menu kontekstowe i wybieramy Breakpoint -> Insert Breakpoint). Breakpoint pojawi się zarówno w oknie Call Stack jak i na marginesie edytora kodu. W odwrotnym scenariuszu breakpoint postawiony w kodzie pojawi się w oknie Call Stack dopiero kiedy spowoduje zatrzymanie aplikacji. W każdej metodzie może zostać umieszczony wiele pułapek i trudno by je wszystkie umiescić w oknie Call Stack.

Przy pomocy okna Call Stack można również zarządzać pułapkami czyli określić warunek zatrzymania, filtr, wyłączyć pułapkę itd. W tym celu klikamy prawym przyciskiem wybraną pułapkę, z menu kontekstowego wybieramy Breakpoint, dalej interesującą nas komendę np.: Disable Breakpoint w celu wyłączenia pułapki lub Condition... w celu określenia warunku zatrzymania się pułapki.



Okno Call Stack umożliwia również ustawienie punktu śledzenia (ang. tracepoint). Tracepoint to w gruncie rzeczy specjalny breakpoint, który nie powoduje zatrzymania wykonania ale wypisanie komunikatu do okna Output. W celu ustawienie tracepoint z poziomu okna Call Stack wywołujemy menu kontekstowe i wybieramy Breakpoint -> Insert Tracepoint. Ustawienie punktu śledzienia z poziomu edytora kodu rozpoczyna się od ustawienia zwykłego brakpoint'a. Następnie należy wywołać menu kontekstowe, dalej wybrać When Hit... (sposób ten działa również w przypadku okna Call Stack). W oknie, które pojawi się zaznaczamy pola wyboru jak na rysunku:

Jeśli nie zaznaczymy pola wyboru Continue execution utworzymy zwykły breakpoint, który dodatkowo będzie powodował wypisanie komunikatu do okna Output. Tracepoint'y reprezentowane są przez romby:

W polu tekstowym okna When Breakpoint Is Hit możemy wpisać dowolny tekst, wyświetlić dowolną zmienną lub użyć jednego ze specjalny słów kluczowych. Ale po kolei. Aby wyświetlić zmienną należy wpisać { nazwa }. Słowa kluczowe wpisujemy natomiast po znaku $. Na przykład $PID służy do wypisania identyfikatora procesu. Nie będę omawiał wszystkich słów kluczowych ponieważ, jak widać na powyższym rysunku, ich opis można znaleźć w oknie When Breakpoint Is Hit. Zwrócę tylko jeszcze uwagę na bardzo ciekawą możliwość wyświetlenia stosu przy pomocy słowa kluczowego $CALLSTACK oraz na to, że przy tworzeniu komunikatu jesteśmy ograniczeni tylko do jednej linii.

Run To Cursor

Run To Cursor to bardzo znane polecenie, które umożliwia rozpoczęcie debugowania i wskazanie miejsca w kodzie, w którym debugger ma się zatrzymać (pod warunkiem, że nie zatrzyma się wcześniej z powodu breakpoint'a). Niewiele osób jednak wie, że polecenie to jest dostępna z poziomu okna Call Stack. Pozwala wskazać, do którego miejsca chcemy zwinąć stos. Wystarczy wybrać interesującą nas ramkę/metodę i z menu kontekstowego wybrać tę komendę.

Wartość argumentów

Inną bardzo fajną cechą okna Call Stack jest to, że podaje ono wartości przekazanych do funckji argumentów. Jeśli wartości argumentów nie są widoczne powinniśmy w onie Call Stack wywołać menu kontekstowe, a następnie zaznaczyć Show Parameter Values. No dobrze, w przypadku typów prostych jest to z pewnością przydatne ale co z typami złożonymi. Domyślnie zostanie wyświetlona po prostu nazwa typu ale można to zmienić korzystając z atrybutu DebuggerDisplayAttribute. Dla tych co nie wiedzą jest to jeden z wielu atrybutów, który pozwala na dostosowanie debuggera do naszych potrzeb (kolejny dobry temat na oddzielny post, a nawet kilka :)). Najłatwiej wyjaśnić to na przykładzie. Posłużmy się kodem jak poniżej:
public class Test
{
   private int value;

   public int Value
   {
      get { return this.value; }
   }

   public Test(int value)
   {
      this.value = value;
   }
}
Wartości typu Test zostaną przedstawione w taki o to mało interesujący sposób:

ConsoleApplication.Program.Equals(a = {ConsoleApplication.Program.Test}, b = {ConsoleApplication.Program.Test})

Wystarczy jednak zmodyfikować definicję klasy Test jak poniżej:
[DebuggerDisplay("Value = {value}")]
public class Test
{
...
}
Aby osiągnąć taki efekt:

ConsoleApplication.Program.Equals(a = Value = 1, b = Value = 1

Technika ta działa nie tylko w przypadku okna Call Stack ale również w przypadku innych okien debuggera czyli np.: Quick Watch.

Unwind To This Frame

Unwind To This Frame to jedna z komend dostępna w menu kontekstowym okna Call Stack. W większości przypadków kiedy wywołamy menu jest wyszarzona. Dostępna staje się tylko w przypadku kiedy zostanie rzucony wyjątek. W momencie rzucenia wyjątki środowisko najczęściej zatrzymuje wskazując linię w kodzie, w której wyjątek został wygenerowany (w szczególności może się nie zatrzymać jeśli wyjątek został obsłużony, a my nie mamy zaznaczonej opcji zatrzymywania na wszystkich wyjątkach). W oknie Call Stack ramka, w której został rzucony wyjątek wskazywana jest przy pomocy żółtej strzałki. Jeśli wyjątek został rzucony poza naszym kodem ostatnia ramka wskazująca nasz kod zostanie oznaczona strzałką zieloną .

Kiedy środowiska zatrzyma się już z powodu wyjątku możemy w oknie Call Stack wybrać interesującą nas ramkę, wywołać menu i wybrać komendę Unwind To This Frame. Spowoduje to zwinięcie stosu do wybranej ramki (działanie podobne do Run To Cursor) i umożliwi np.: zdiagnozowanie przyczyny wyjątku, modyfikację zmiennych lokalnych tak aby wyjątek nie został rzucony itd.

Polecenie Unwind To This Frame nie jest dostępne dla wszystkich ramek. Stos można zwinąć do każdej ramki powyżej ostatniej ramki, która wskazuje miejsce w naszym kodzie (pomiędzy żółtą i zieloną strzałką :)). Czyli jeśli wyjątek został rzucony bezpośrednio w naszym kodzie polecenie będzie dostępne tylko dla jednej ramki. Z kolei dla przypadku pokazanego niżej:

System.Xml.Serialization.XmlSerializer.Deserialize(xmlReader, encodingStyle, events)
System.Xml.Serialization.XmlSerializer.Deserialize(stream)
ConsoleTest.Program.Main(args)
...


Stos możemy zwinąć do jednej z metod Deserialize() albo do metody Main() w naszym kodzie.

Omawiając tą funkcjonalność należy koniecznie napisać o exception assistant. Całkiem możliwe, że większość użytkowników Visual Studio nigdy o nim nie słyszała. W sumie nie dziwne ponieważ jest domyślnie włączony (Tools -> Options -> Debugging -> General -> Enable the exception assistant). A do czego służy? Poniżej zamieszczam okienka wyświetlone przez środowisk w momencie rzucenia wyjątku kiedy asystent jest wyłączony (pierwszy obrazek) i kiedy jest włączony (drugi obrazek):



Mam nadzieję, że zalety asystenta są oczywiste :) ale asystent to nie tylko ładne okienko. Jeśli jest włączony w momencie rzucenia wyjątki środowisko wskaże zawsze linię w naszym kodzie nawet jeżeli prawdziwe źródło wyjątku jest gdzie indziej. Dzięki temu możemy po prostu "złapać" żółtą strzałkę, wskazującą w głównym oknie ostatnio wykonaną instrukcję, i przeciągnąć ją kilka linijek wcześniej lub kilka linijek dalej. Innymi słowy możemy ominąć wyjątek lub prześledzić jak został spowodowany. Przy włączonym asystencie polecenie Unwind To This Frame staje się trochę mniej ponieważ przydatne ale czasami możemy chcieć wyłączyć asystenta. W każdym razie dobrze zdawać sobie sprawę z jego istnienia.

Opisane techniki testowałem w środowiskach Visual Studio 2005 oraz Visual Studio 2008.

19/02/2009

Wycieki obiektów GDI

Home

Ostatnio zajmowałem się w pracy nieprzyjemnym problem z wyciekiem obiektów GDI. Sytuacja wydawała się dziwna ponieważ wyglądało na to, że metoda Dispose() była wołana wszędzie gdzie było to potrzebne. Po mówiąc szczerze dłuższym czasie dowiedziałem się o dwóch istotnych rzeczach:
  • Kontrolka TreeView zawiera bug polegający na tym, że jeżeli włączymy pokazywanie pól wyborów (ang. CheckBox) dla węzłów drzewa to zostaną zaalokowane cztery obiekty GDI. Nie zostaną one jednak zwolnione przy wywołaniu Dispose(). Ciekawą dyskusję na ten temat można znaleźć tutaj. Co gorsza jeśli poszperamy na sieci okaże się, że błąd jest znany od co najmniej kilku lat!
  • Drugi bug, o którym chciałem powiedzieć dotyczy klasy ImageList. Otóż klasa ta ma problemy ze zwalnianiem zasobów dla umieszczonych w niej obrazków/ikon, co doprowadza oczywiście do wzrostu liczby obiektów GDI. O błędzie tym możemy przeczytać na stronach firmy Microsft. Rozwiązanie, a raczej obejście problemu polega na wywołaniu metod GC.Collect() oraz GC.WaitForPendingFinalizers() po wywołaniu Dispose() dla instancji ImageList. Rozwiązanie działa ale korzystanie z metody GC.Collect() może mieć negatywny wpływ na wydajność aplikacji. Co gorsza może wystąpić jeszcze mniej pożądany efekt. Opieram się tutaj na doświadczeniach kolegi. Zgodnie z nimi używanie GC.Collect() w aplikacjach korzystających z kontrolek ActiveX może doprowadzić do ich zawieszenia w losowym momencie.
    ...
    imageList.Dispose();
    
    GC.Collect();
    GC.WaitForPendingFinalizers();
    ...
    
Z opisanych przeze mnie rzeczy trzeba zdawać sobie sprawę i być uważnym przy stosowania klasy TreeView, a w szczególności klasy ImageList. W przypadku posługiwania się TreeView można pokusić się jeszcze o użycie, zaproponowanej we wspomnianej przeze mnie dyskusji, klasy LeaklessTreeView (testowałem i wygląda, że działa).

17/02/2009

operator==

Home

Pomysł na napisanie tego posta podsunął mi kolega z pracy. Na początek fragment klasy, która we wzorcowy sposób dostarcza własnej implementacji operatora równości:
public class Test
{
  private int i;
  
  public Test(int i)
  {
    this.i = i;
  }

  public static bool operator==(Test a, Test b)
  {
    if (System.Object.ReferenceEquals(a, b))
      return true;

    if (((object)a == null) || ((object)b == null))
      return false;

    return a.i == b.i;
  }
  
  ...
}
Oczywiście potrzebna jest jeszcze implementacja operatora nierówności !=. Chciałbym też zwrócić uwagę, ponieważ sam zapomniałem o tym pisząc ten przykład :), że implementując operator równości (czy też nierówności) kiedy wykonujemy porównanie takie jak a == b, a == null czy b == null musimy zrzutować testowane obiekty do typu object. W innym wypadku nastąpi rekurencyjne wywołanie naszej implementacji operatora. Rzutowanie zapewni, że zostanie wywołana implementacja bazowa - operatory nie działają jak metody wirtualne. Inne rozwiązanie to użycie metody System.Object.ReferenceEquals. Teraz popatrzmy na kod poniżej i odpowiedzmy sobie na pytanie jaką wartość przyjmą zmienne lokalne res oraz res2:
public class Program
{
  public static bool MyEquals(T a, T b) where T : class
  {
    return a == b;
  }
  
  public static void Main(string[] args)
  {
    Test a = new Test(1);
    Test b = new Test(1);

    bool res = a == b;
    bool res2 = MyEquals(a, b);
  }
}
Odpowiedź brzmi oczywiście true oraz false. Piszę o tym również dla siebie ponieważ sam musiałem się zastanowić jaki będzie wynik. Pierwszy rezultat jest chyba oczywisty i nie wymaga żadnych tłumaczeń. Po prostu wywołany zostanie zaimplementowany przez nas operator. A co z wywołaniem metody generycznej MyEquals. Może się wydawać, że skoro jest to metoda generyczna to zostanie wywołany operator równości dla typu przekazanego jako argument typu generycznego (ang. generic type parameters). Argument typu generycznego to to co podstawiamy pod T czyli w tym przypadku Test.

Jest jednak inaczej. W momencie kompilacji kompilator nie wie co zostanie podstawione pod T. W związku z tym generowany jest kod sprawdzający równość referencyjną. Na poziomie IL oznacza to użycie instrukcji ceq. Jeśli zmienimy kod metody MyEquals tak jak poniżej kompilator będzie już mógł wybrać operator równości dostarczony przez nas zamiast bazowego.
public static bool MyEquals(T a, T b) where T : Test
{
  return a == b;
}

04/02/2009

Velocity (cz. 1)

Home

Velocity (z angielskiego prędkość) to mało znany projekt firmy Microsoft, którego celem jest stworzenie rozproszonej pamięci podręcznej czyli rozproszonego cache'a (nie należy mylić z projektem fundacji Apache, którego celem jest opracowanie procesora szablonów). Innymi słowy Velocity ma zwiększyć niezawodność i wydajność działania aplikacji poprzez replikację, rozproszenie oraz zapewnienie szybkiego dostępu do danych. Obecnie dostępna wersja produktu to dopiero tzw. CTP2 (ang. Community Technology Preview) czyli wersja udostępniona społecznością w celach testowych, zgłaszania uwag oraz postulatów. Nie wiadomo kiedy dokładnie pojawi się końcowy produkt ale moim zdaniem już teraz Velocity jest godne zainteresowania, a postem tym chciałbym rozpocząć krótki cykl trzech może czterech wpisów dotyczących tej technologii.

Na początek warto wymienić najważniejsze (moim zdaniem) rzeczy charakteryzujące Velocity. Zestaw ten oparłem na własnych doświadczeniach oraz na tym czego dowiedziałem się z dokumentacji i artykułów.
  • W pamięci podręcznej może zostać umieszczony każdy serializowalny obiekt.
  • Prosty interfejs programistyczny.
  • Wspiera PHP, C#, C++...
  • Możliwość wykorzystania w aplikacjach stacjonarnych, konsolowych i webowych. Przewidziana jest też integracja z dostawcą sesji ASP.NET.
  • Możliwość zarządzania i konfigurowania pomocy konsoli PowerShell.
  • Możliwość uruchomienia klastra złożonego z kilku, a nawet kilkunastu maszyn. Konfiguracja klastra może znajdować się w bazie danych lub w pliku konfiguracyjnym umieszczonym w współdzielonym katalogu.
  • Kilka typów cache'a: replikujący, partycjonujący. Typ replikujący po umieszczeniu w nim porcji danych tworzy jej kopię i umieszcza na węzłach klastra (dobre dla danych wolno-zmiennych). Typ partycjonujący rozmieszcza dane równomiernie pomiędzy poszczególne węzły klastra (dobry dla danych szybko-zmiennych). W wersji CTP2 dostępny jest tylko typ partycjonujący. Dane w cache'u partycjonującym również mogą być replikowane w celu zwiększenia bezpieczeństwa na wypadek awarii (kopia główna i poboczne).
  • Velocity wspiera silne i słaby model spójności dostępu do danych (w zależności od typu cache'a). Model spójności to umowa pomiędzy składnicą danych, a klientem mówiąca, że jeżeli klient będzie postępował zgodnie z określonymi regułami to dane nie ulegną uszkodzeniu. Słaby model spójności bazuje na jawnych zmiennych synchronizacyjnych. Silne modele spójności są implementowane przezroczyście dla klienta.
  • Operacje replikacji, partycjonowania danych, a także synchronizowania ich kopii są w pełni automatyczne i przezroczyste dla klienta.
  • Dostępne podejście optymistyczne (oparte o znaczniki, kto pierwszy ten lepszy) i pesymistyczne (wykorzystujące jawne operacje synchronizacyjne) do synchronizowania współbieżnych dostępów do danych.
  • Możliwość dynamicznego rekonfigurowania klastra poprzez dodawanie nowych węzłów.
  • Automatyczne rekonfigurowanie klastra w wypadku awarii.
  • Automatyczne równoważenie obciążenia węzłów klastra.
  • Velocity może zostać uruchomione jako usługa systemowa lub działać w procesie aplikacji.
  • Można konfigurować ilość pamięci zużywanej przez Velocity.
Osobiście pracowałem z Velocity zbyt krótko aby potwierdzić wszystkie te cechy. W szczególności testowałem tylko klaster złożony z kilku komputerów. Nie wiem też jak sprawuje się dynamiczna rekonfiguracja klastra i automatyczne równoważenie obciążenia.

W następnym poście przedstawię terminologię związaną z Velocity: gospodarz, główny gospodarz, nazwany cache itd. oraz wspomnę trochę o procesie instalowania i konfigurowania. W trzecim poście zajmę się API. Powiem również o wadach i rzeczach, które powinny moim zdaniem zostać zmienione w wersji finalnej. Dla niecierpliwych i żądnych wiedzy garść linków: