27/11/2008

throw; vs. throw ex;

Home

Nie każdy zdaje sobie z tego sprawę ale poniższe dwie konstrukcje mają inną semantykę:

         try
         {
            ...
         }
         catch(Exception ex)
         {
            throw;
         }
         
         try
         {
            ...
         }
         catch(Exception ex)
         {
            throw ex;
         }
         
Różnica jest taka, że stosując pierwszą z nich nie tracimy informacji zawartej w stosie wywołań (stack trace). To znaczy, że jeśli ponownie rzucony przez nas wyjątek zostanie złapany w kolejnym bloku try/cacth to będzie tam dostępna pełna informacja o stosie wywołań począwszy od pierwotnego źródła wyjątku. W drugim przypadku stos wywołań będzie zawierał ograniczoną informację - będzie wyglądał tak, jakby pierwotnym źródłem wyjątku była metoda, w której został on złapany i ponownie rzucony!

24/11/2008

Zakamarki Visual Studio 2005/2008 (cz. 2)

Home

Zapraszam do zapoznania się z kolejną porcją ciekawych i mało znanych funkcji Visual Studio.

Breakpoint w pętli

Bardzo przydaną funkcją jest możliwość postawienia breakpoint'a w definicji pętli for lub foreach. Załóżmy, że mamy taki kod:
for(int i = GetValue(); i < GetLimit(); i++)
{
  ...
}
Domyślne zachowanie środowiska jest takie, że po kliknięciu linii, w której znajduje się początek pętli i naciśnięciu przycisku F9 breakpoint zostanie ustawiony na części inicjalizacyjnej pętli czyli uzyskamy taki efekt:
for(int i = GetValue(); i < GetLimit(); i++)
{
  ...
}
Czasami, a nawet częściej niż czasami chcielibyśmy aby debugger zatrzymał się w części sprawdzającej warunek pętli. Nic prostszego. Wystarczy przesunąć kursor i ponownie nacisnąć F9. Uzyskamy efekt jak poniżej:
for(int i = GetValue(); i < GetLimit(); i++)
{
  ...
}
Oczywiście debugger możemy zatrzymać również w instrukcji interacji. Podobnie możemy postąpić z pętlą foreach.

Testowanie przy pomocy okna Immediate

Ciekawym sposobem na szybkie testowanie metod statycznych jest użycie okna Immediate. Jeśli nie jest ono standardowo widoczne to znajdziemy je w Debug -> Windows. Po pierwsze okno to pozwala w czasie debugowania wywoływać metody obiektów, zmieniać ich właściwości itd. Po drugie, co jest nawet ciekawsze, umożliwia wywołanie metody statycznej kiedy środowisko nie znajduje się w trybie debugowania. Wykonanie takiej operacji spowoduje uruchomienie debugera i o ile, w metodzie statycznej znajduje się breakpoint, jego zatrzymanie. Dzięki temu nie musimy tracić czasu na pisanie krótkich programików tylko po to aby przetestować daną metodę statyczną. Po trzecie co jeszcze ciekawsze okno Immediate pozwala również w podobny sposób testować zwykłe metody klas. Załóżmy, że napisaliśmy klasę Test i zdefiniowaliśmy w niej metodę Fun(). W oknie Immediate może wpisać:
Test t = new Test(); 
t.Fun(); 
Podobnie jak wcześniej. Jeśli w metodzie znajdował się breakpoint debugger zatrzyma się na nim. Jesli nie, metoda zakończy swoje działanie. Oczywiście technika ta pozwala testować tylko stosunkowe proste scenariusze ale tak czy inaczej ułatwia i przyspiesza tworzenie dobrego kodu.

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

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.