22/11/2008

ADO.NET Data Services, Jak to ugryźć?

Home

Wszystkich zainteresowanych rozpoczęciem zabawy z technologią Microsoft'u ADO.NET Data Services (nazwa kodowa Astoria) chciałbym zachęcić do zapoznania się z serią filmików spod znaku How Do I:
Astoria umożliwia naprawdę łatwy i szybki sposób udostępniania danych z różnych źródeł w sieci (w szczególności może to być oczywiście baza relacyjna). Dostęp do danych został zorganizowany zgodnie z podejściem typu REST czyli upraszczając aby uzyskać dostęp do zasobu musimy znać jego URI. Każdy z filmików pokazuje krok po kroku jak coś zrobić przy pomocy ADO.NET Data Services. Obejrzenie każdego z nich zajmie wam kilkanaście minut (oczywiście trzeba znać język angielski). Bardziej dogłębne (i znacznie dłuższe) wprowadzenie znajdziemy tutaj Developing Applications Using Data Services. Aby rozpocząć używanie Astorii potrzebujemy Visual Studio 2008 z Service Pack 1. Wersję Express już z Service Pack 1 znajdziemy tutaj.

20/11/2008

Tajemnica yield

Home

Czy zastanawialiście się kiedyś jak działa słowo kluczowe yield? Jeśli ktoś nie kojarzy tej konstrukcji to w telegraficznym skrócie pozwala ona (między innymi) w bardzo łatwy sposób zaimplementować interfejs IEnumerable. Interfejs ten wymagana dostarczenia tylko jednej metody, która powinna zwrócić instancję klasy implementującej IEnumerator. Zaimplementowanie tego interfejsu nie powinno przysporzyć znacznych trudności ale wymaga już trochę większego nakładu pracy. Przykładowe, uproszczone użycie yield mogłoby wyglądać tak:
public class Counter : IEnumerable
{
  private int i = 0;

  public Counter(int i)  
  {
    this.i = i;
  }

  public IEnumerator GetEnumerator()
  {
    while(i>0)
      yield return i--;
  }
}
Tylko tyle i aż tyle. Nie musimy pisać kodu dla metod MoveNext, Reset czy też właściwości Current wymaganych przez IEnumerator. Zamiast tego otrzymujemy kilkulinijkowy elegancki kod. Możemy oczywiście napisać teraz coś w tym rodzaju:
foreach (int i in new Counter(10))
  Console.WriteLine(i);
Zanim zaczniecie czytać dalej zastanówcie się teraz przez chwilę w jaki sposób to działa. Teraz możemy przejść do clue tego posta. Otóż okazuje się, że słowo kluczowe yield to nic innego jak lukier syntaktyczny. Nie kryje się zanim żadna magia. Po prostu kompilator po napotkaniu yield generuje dynamicznie kod enumeratora. Możemy to bardzo łatwo zobaczyć korzystając z reflektora Lutz Roeder’s Reflector. To co zobaczymy będzie koncepcyjnie podobne do kodu poniżej.
public class Counter
{
...
  public IEnumerator GetEnumerator()
  {
    //Utworzenie enumeratora
    InnerEnumerator ie = new InnerEnumerator(0);
    //Ustawienie wskazania na obiekt, po którym będziemy enumerować
    ie.current = this;

    reutrn ie;
  }

  private sealed class InnerEnumerator : IEnumerator
  {
    //Stan w jakim znajduje się enumerator
    //0 - stan początkowy
    //1 - stan pośredni
    //-1 - stan końcowy
    private int state;
    //Ostatnia wartość zwrócona przez enumerator
    private int current;
    //Obiekt, po którym będziemy enumerować
    public Counter counter;

    public InnerEnumerator(int state)
    {
      //Ustawienie stanu inicjalnego
      this.state = state;
    }

    public bool MoveNext()
    {
      switch (this.state)
      {
        case 0:
        //Jeśli warunek początkowy rozpoczęcia działania enumeratora 
        //nie będzie spełniony to przechodzimy do stanu końcowego
        this.state = -1;
        //Jeśli są jeszcze jakieś wartości do odwiedzenia przez enumerator
        while (this.counter.i > 0)
        {
          //Wyznacz kolejną wartość
          this.current = this.counter.i--;
          //Być może są jeszcze jakieś wartości do odwiedzenia
          //dlatego ustawiamy stan pośredni
          this.state = 1;
          return true;
          LABEL:
          //Jeśli nie będzie już wartości do odwiedzenia to 
          //należy zakończyć pracę enumeratora
          this.state = -1;
        }
        break;

        case 1:
          //Kontynuujemy pracę enumeratora
          goto LABEL;
      }

      //Enumerator odwiedził wszystkie elementy
      return false;
    }

    public object Current
    {
      get{ return this.current; }
    }
    ...  
  }
...
}
Z kodu usunąłem niepotrzebne w tym kontekście fragmenty i zmieniłem go, żeby był prostszy w zrozumieniu. Wygenerowany kod jest prawidłowy jako MSIL ale jako C# nie skomiluje się ze względu niedozwolone użycie instrukcji goto. Całość jest chyba łatwa do zrozumienia, a najistotniejsza jest metoda MoveNext(), w której tak naprawdę możemy zobaczyć to co napisaliśmy w GetEnumerator. Każda iteracja pętli powoduje przejście do następnego elementu. Polecenia skoku użyto aby przy kolejnych wywołaniach MoveNext wskoczyć do środka pętli i kontynuować jej wykonanie. Proste, nie :)

Pożytki płynące z używania Process Monitor'a

Home

W poście tym chciałbym zachęcić do używania darmowego narzędzia Process Monitor, które umożliwia monitorowanie wszelkiej aktywności w systemie operacyjnym dotyczącej zasobów takich jak: pliki, klucze rejestru, połączenia sieciowe itd.

Pożytki płynące z tego narzędzia opiszę na swoim przypadku. Ostatnio napisałem prostą bibliotekę, która parsuje plik Xml, przetwarza jego zawartość przy pomocy transformacji Xsl w celu stworzenia plików Html i finalnie uruchamia kompilator pomocy w celu wytworzenia pliku chm. Biblioteka nie jest skomplikowana i bardzo szybko udało się uzyskać pożądany efekt (prawie). Niestety ale okazało się, że kompilator pomocy nie potrafi przetworzyć wszystkich plików Html i generuje dla nich błąd HHC5003: Error: Compilation failed while compiling... Co ciekawe efekt ten był obserwowany tylko w przypadku programowego uruchomienia kompilatora przy pomocy klasy Process. Przy ręcznym uruchamiania wszystko działało prawidłowo. Po bliższym przyjrzeniu okazało się, że kompilator zgłaszał błędy zawsze dla tych samych plików. W przypadku kiedy lista plików została powiększona błąd zaczął być zgłaszany dla nowych plików! Pliki, które wcześniej zdawały się nieprawidłowe okazywały się nagle jak najbardziej w porządku.

Nie udało mi się znaleźć dokładnych informacji na temat zgłaszanego przez kompilator błędu z wyjątkiem tego, że przyczyną może być brak dostępu do pliku. Postanowiłem więc przyjrzeć się procesowi kompilacji przy pomocy Process Monitor'a. Ponieważ aplikacja w trybie domyślnym wyświetla ogromną ilość danych (setki tysiące wierszy) postanowiłem skupić się tylko na wpisach dotyczących jednego z plików, dla których kompilator zgłaszał błąd. Po włączeniu filtrowania uzyskałem taki wynik:



Na niebiesko oznaczyłem wiersz, w którym widać, że kompilator nie uzyskał dostępu do pliku z powodu błędu SHARING VIOLATION. Błąd ten oznacza, że plik był w tym momencie w użyciu. Ponieważ problematyczny plik Html był generowany w całości przez mój kod mogłem założyć, że błąd tkwi po mojej stronie. Jak się okazało popełniłem akademicki błąd i nie zamykałem strumienia do pliku (do tej pory biję się w pierś). Oczywiście konstrukcja using rozwiązała problem.

Cały czas pozostaje jednak pytanie czemu błąd pojawiał się tylko dla określonych plików. Strumień nie był przecież zamykany dla każdego dynamicznie wygenerowanego pliku Html. W tym przypadku odpowiedź też nie jest skomplikowana - musiał zadziałać mechanizm automatycznego zwalniania pamięci garbage collector.

19/11/2008

Zakamarki Visual Studio (cz. 1)

Home

Visual Studio to potężne narzędzie, o ogromnych możliwościach, które pozwala tworzyć i debugować programy w łatwy i przyjemy sposób. Dobra znajomość swojego środowiska pracy do podstawa dla każdego programisty. Dlatego poniżej zamieszczam opis kilku "zaawansowanych" narzędzi udostępnionych w Visual Studio. Tak naprawdę prezentowane przeze mnie techniki nie są ani trudne, ani skomplikowane w użyciu. Nie ulega jednak wątpliwości, że są stosunkowo mało znane. Działają poprawnie w przypadku języka C# (dla innych języków może być inaczej).

Okienko Find

Z pewnością każdy kiedyś korzystał z tego narzędzia aby szybko wyszukać w kodzie interesującą go frazę. Ale możliwości okienka Find są znacznie większe. Po pierwsze wpisanie do niego nazwy jakieś metody i naciśnięcie przycisku F9 spowoduje wstawienie breakpoint'a do każdej metody o podanej nazwie. Moim zdaniem bardzo przydatna funkcjonalność jeśli mamy w projekcie jedną lub więcej, wielokrotnie przeciążonych metod. Do okienka Find możemy również wpisać nazwę pliku (razem z rozszerzeniem). Teraz jeśli naciśniemy kombinację Ctrl+Shift+G i plik o podanej nazwie istnieje to zostanie otworzony gotowy do edycji. Przydaje się przy dużym drzewie projektów z rozbudowaną hierarchią plików i folderów.

Podglądanie cudzych metod

Przypuśćmy przez chwilę, że pracujemy ze złośliwym programistą, który nie chce udostępnić innym swoich kodów. Przypuśćmy dalej, że w kodzie złośliwego programisty jest generowany wyjątek. Wyjątek ten powstaje głęboko w kodzie złośliwego programisty i oznacza, że do metody został przekazany ciąg znaków o błędnym formacie. Wspomniany ciąg znaków dostarczany jest przez nas i może to być np.: connection string.

Co więcej programista jest tak uparty, że twierdzi, że to nie jego wina oraz, że z pewnością przekazujemy do jego metody złe dane. Co gorsza jest na tyle sprytny, że zabezpieczył swoje biblioteki przed programem takim jak Lutz Roeder’s Reflector. Tak na marginesie to genialne narzędzie, niezbędne w pracy każdego programisty .Net. Należy jeszcze dodać, że ten programista to syn szefa :)

My wiemy swoje i on wie swoje. Nie mamy dostępu to kodu biblioteki złośliwego programisty ale oczywiście znamy stack trace. Znamy więc nazwy kolejno wołanych metod aż do wystąpienia wyjątku. Jedyna nasza szansa to udowodnić złośliwemu programiście, że nasze dane są prawidłowe i to w jego kodzie jest błąd.

I tutaj wkracza do akcji Visual Studio. Okazuje się, że można postawić breakpoint w metodzie, dla której nie mamy kodu źródłowego. Kiedy debugger zatrzyma się będziemy mogli w oknie Locals zobaczyć wartości wszystkich argumentów przekazanych do metody! W celu postawienia takiego breakpoint'u korzystamy z bardzo prostego sposobu: otwieramy okno New Breakpoint, w polu Function wpisujemy nazwę metody, naciskamy przycisk Ok i gotowe. Do okna New Breakpoint dostajemy się z poziomu standardowego okna Breakpoints (Debug -> Windows -> Breakpoints) wybierając przycisk New, a następnie polecenie Brak at Function...

Okno Call Stack

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 metodę i z menu kontekstowego wybrać tę komendę. W bardzo podobny sposób można przy pomocy okna Call Stack postawić breakpoint. W tym celu podobnie jak wyżej wybieramy interesującą nas metodę, zaznaczamy ją i naciskamy przycisk F9 (albo wywołujemy menu kontekstowe i wybieramy Breakpoint -> Insert Breakpoint).

Podsumowanie

Mam nadzieję, że opisane przeze mnie narzędzia i techniki okażą się przydatne. W następnej części opiszę kolejną porcję ciekawych funkcji Visual Studio.

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