RX extensions w przykładach

RxExtension – to biblioteka od Microsoftu ułatwiająca programowanie asynchroniczne. Opiera się na istniejących interfejsach IObservable oraz IObserver.
W RX wiadomości są traktowane jako strumienie danych, do których należy się przypiąć i reagować na pojawienie się nowej wiadomości. Najnowsza wersja ma już cyferkę 2, ale nie jest jeszcze oznaczona jako stabilna. Ja do nauki wykorzystałem wersję 1 oraz książeczkę dostępną na stronie RX – Dev Labs Hands On. Na Channel9 są jakieś filmy na temat RX. Poniżej pokaże kilka przykładów i opis jak korzystać z RX, źródła pochodzą z książki, będą dostępne razem z książką i projektem MSVC. Drobna uwaga co do książki i źródeł: w książce podane są dwa pliki do których należny dodać referencję, aktualna wersja RX dostarcza tylko jednej biblioteki – System.Reactive, natomiast System.CoreEx jest już w systemie. Ja korzystałem z .net 4.0. Podobnie kilka metod wymienionych w książce nie jest już dostępne w związku z czym zostały zamienione na inne dostępne i spełniające te same wymagania.

Zaczynamy od poznania interfejsów:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  static void Main(string[] args)
   2:  {
   3:      IObservable<int> source = Observable.Empty<int>();
   4:      IObserver<int> handler = null;
   5:   
   6:      IDisposable subscription = source.Subscribe();
   7:      Console.WriteLine("Press ENTER to unsubscribe and dispose");
   8:      Console.ReadLine();
   9:   
  10:      subscription.Dispose();
  11:  }

RX opiera się o dwa interfejsy IObservable oraz IObserver. Klasa Observable pochodzi System.Reactive.Linq. Kod powyżej nie jest zbytnio porywający, ale pokazuje, że po subskrypcji do źródła danych otrzymujemy instancję IDisposable, którą trzeba będzie wywalić do kosza po zakończeniu pracy. Później będzie to zrobione przy użyciu mechanizmu using.

Interfejs IObserver definiuje trzy metody, które muszę zostać zdefiniowane:

   1:  public interface IObserver<in T>
   2:  {
   3:      void OnCompleted();
   4:      void OnError(Exception error);
   5:      void OnNext(T value);
   6:  }
  • OnCompleted: źródełko wyschło
  • OnNext:  nowa informacja
  • OnError: coś poszło nie tak

Podczas dopisywania się do obiektu, który ma dostarczać informacji można wykorzystać wyżej wymieniony interfejs lub skorzystać z wyrażeń lambda. To drugie podejście jest prostsze, dodatkowo nie wymagane jest definiowanie dla wszystkich metod z interfejsu.
Na początek pełna deklaracja:

   1:  static void Main(string[] args)
   2:  {
   3:      IObservable<int> source = Observable.Empty<int>();
   4:   
   5:      IDisposable subscription = source.Subscribe(
   6:          x=> Console.WriteLine("Has new value {0}", x),
   7:          ex=>Console.WriteLine("Exception caught {0}", ex.Message),
   8:          ()=>Console.WriteLine("No more items")
   9:          );
  10:   
  11:      Console.WriteLine("ENTER to dispose");
  12:      subscription.Dispose();
  13:  }

W tym przypadku od razu zostanie wywołana metoda OnCompleted, ponieważ źródło danych jest puste. Po zakończeniu pracy zwalniamy zasoby przez wywołanie Dispose na obiekcie zwróconym po subskrypcji. Co ważne, aby móc korzystać z rozszerzeń dla RX należy dodać referencję oraz using do System.Reactive.Linq.

Jednoelementowa kolekcja intów:

   1:  static void Main(string[] args)
   2:  {
   3:      IObservable<int> source = Observable.Return(42);
   4:   
   5:      var subscriber = source.Subscribe(
   6:          x=>Console.WriteLine("Value: {0}",x),
   7:          ex=>Console.WriteLine("Exception: {0}", ex.Message),
   8:          ()=>Console.WriteLine("end!")
   9:          );
  10:   
  11:      Console.WriteLine("ENTER to dispose");
  12:      subscriber.Dispose();
  13:  }

Kod powyżej zwróci raz wartość 42 (sens życia), a następnie zakończy poprzez wywołanie OnCompleted. Następnie zwolnienie zasobów. Ponownie wykorzystanie Reactive.Linq do stworzenia jednoelementowej kolekcji.

Aby zrobić coś ciekawszego można podmienić kod definiujący źródło na coś takiego:

   1:  IObservable<int> source = Observable.Range(5, 7);

Spowoduje do wygenerowanie cyfr od 5 do 11, następnie zakończy przez OnCompleted.

Aby nie zanudzać prostymi przykładami, RX umożliwia stworzenie obserwowanej pętli for, robi się to w taki sposób:

   1:  static void Main(string[] args)
   2:  {
   3:      IObservable<int> source = Observable
   4:          .Generate(0,          // initial state
   5:          i => i < 10,    // condition
   6:          i => i + 1,        // iteration step
   7:          i => i * i);       // iteration operation
   8:   
   9:      using (var s = source.Subscribe(
  10:          x => Console.WriteLine(x)    // only the working stuff will be handled
  11:                                          // no errors and no exceptions
  12:                                          // no information about sequence finish either
  13:          )) { };
  14:  }

Coraz ciekawiej co nie?
Zaczynamy od 0, następnie warunkiem jest i mniejsze od 10, w każdym kroku i będzie zwiększane co 1, a wynikiem operacji ma być i*i, wynik mnożenia nie jest zapisywany do i, tylko zwracany jako wynik. W przeciwnym wypadku pętla skończyła by się za wcześnie.
Podczas dopinania się do źródła definiujemy tylko metodę OnNext, która przyjmuje jeden parametr, w ten sposób nie zostaniemy powiadomieni o skończeniu się danych lub o wystąpieniu błędu. Dodatkowo wykorzystany zostanie mechanizm using, na który spadnie odpowiedzialność zwolnienia zasobów z IDisposable.

Jeśli zastanawialiście się jak to możliwe że pętla nie zakończy skoro w klamrach using nic nie ma, nie dziwcie się, dla mnie na początku to także było dziwne. Ale jeśli odpalić debuggera i sprawdzić wątki, to wszystkie wywołania są dokonywane z głównego wątku. Dopiero po skończeniu się zasobów w IObservable, zostanie wykonany kod z klamerek.

Żeby nie było nudno, teraz przykład z normalniejszym kodem, takim który jest w klamerkach:

   1:  static void Main(string[] args)
   2:  {
   3:      var source = Observable.Generate(
   4:          0,
   5:          i => i < 10,
   6:          i => i + 1,
   7:          i => i * i,
   8:          i => TimeSpan.FromSeconds(i)
   9:          );
  10:   
  11:      using (var s = source.Subscribe(
  12:          x => Console.WriteLine("next: {0}", x),
  13:          ex => Console.WriteLine("exception: {0}", ex.Message),
  14:          () => Console.WriteLine("no more")))
  15:      {
  16:          Console.WriteLine("ENTER");
  17:          Console.ReadLine();
  18:      }
  19:  }

Dwie zmiany zostały wprowadzone; dodałem 1 sekundowe opóźnienie w generowaniu wartości i, co powoduje że wywoływania zaczynają wreszcie być prawdziwie asynchroniczne. To powoduje, potrzebę umieszczenie Console.ReadLine w ciele using, w przeciwnym wypadku, nie zdążymy odczytać nawet pierwszej wartości. Walnięcie ENTER w trakcie działania dema, spowoduje jego zakończenie.
Okno wątków w debuggerze, pokazują że podczas wołania OnNext, wykorzystywany jest dodatkowy Worker Thread, stworzony przez RX.

Teraz drobna zmiana, do naszej konsoli dołożona zostanie prosta forma (WinForms – przykłady z książki są na tym oparte, więc i ja z tego skorzystałem). Będzie służyć za pośrednika do źródła danych, załóżmy sobie że jest taki kod:

   1:  static void Main(string[] args)
   2:  {
   3:      var label = new Label();
   4:      var form = new Form
   5:      {
   6:          Controls = { label }
   7:      };
   8:   
   9:      var moves = Observable.FromEventPattern<MouseEventArgs>(form, "MouseMove");
  10:   
  11:      using (moves
  12:          .Subscribe(
  13:              x => label.Text = x.EventArgs.Location.ToString(),
  14:              ex => label.Text = ex.Message,
  15:              () => label.Text = "Mouse is over?!" ) )
  16:      {
  17:          Application.Run(form);
  18:   
  19:      }
  20:  }

Pojawia się tutaj prosta forma z tekstem do którego będziemy zapisywać informacje o aktualnym położeniu myszy. To co interesujące znajduje się w linijce 9, gdzie tworzymy źródło danych na podstawie przychodzących zdarzeń z formy, następnie zapisujemy się do nich. Ponieważ w tym przykładnie nie stosowane są żadne specjalne opóźnienia (o nich poniżej), nie potrzeba tutaj wykorzystywać Dispatchera czy obiekty synchronizacji pomiędzy wątkami. W okienku wątków widać, że każde wywołanie metody OnNext realizowane jest w głównym wątku UI, dlatego bezpieczna jest modyfikacja labelki.

   1:  static void Main(string[] args)
   2:  {
   3:      var label = new Label();
   4:      var form = new Form
   5:      {
   6:          Controls = { label }
   7:      };
   8:   
   9:      var moves = Observable.FromEventPattern<MouseEventArgs>(form, "MouseMove");
  10:   
  11:      using (moves
  12:          .Subscribe(
  13:              x => label.Text = x.EventArgs.Location.ToString(),
  14:              ex => label.Text = ex.Message,
  15:              () => label.Text = "Mouse is over?!" ) )
  16:      {
  17:          Application.Run(form);
  18:      }
  19:  }

Najciekawsza jest linia 9, gdzie wydarzenia generowane przez formę/mysz są definiowane jako źródło danych, a następnie w linii 13 wartość ta zostaje wyłuskana z argumentów oraz zapisana w labelce na formatce.

Kolejny przykład będzie rozwinięciem tego powyżej:

   1:  static void Main(string[] args)
   2:  {
   3:      var textbox = new TextBox();
   4:      var form = new Form
   5:      {
   6:          Controls = { textbox }
   7:      };
   8:   
   9:      var moves = Observable
  10:          .FromEventPattern<MouseEventArgs>(form, "MouseMove")
  11:          .Select(e => e.EventArgs.Location);
  12:   
  13:      var texts = Observable
  14:          .FromEventPattern<EventArgs>(textbox, "TextChanged")
  15:          .Select(e => (e.Sender as TextBox).Text);
  16:   
  17:      var msubs = moves.Subscribe(x => Console.WriteLine("mouse position: {0}", x));
  18:      var ksubs = texts.Subscribe(x => Console.WriteLine("Textbox text: {0}", x));
  19:   
  20:      using (new CompositeDisposable(msubs, ksubs))
  21:      {
  22:          Application.Run(form);
  23:      }
  24:  }

Jak widać tutaj ponownie nasłuchujemy na wiadomości generowane przez ruch myszy, ale dodatkowo oczekujemy na wpisywany przez użytkownika tekst w formatce. Warto zauważyć, że w przypadku tekstu, źródłem danych nie jest formatka, ale sama kontrolka. W tym przykładzie pod uwagę bierzemy tylko pozytywne wywołania OnNext. Dodatkowo subskrypcja wykorzystuje projekcję (Select) i ostatecznej rozgrywce (17 i 18) otrzymujemy tylko takie wartości, które są dla nas najbardziej interesujące. Na sam koniec przykładu, wykorzystujemy klasę CompositeDisposable, która umożliwia wykorzystanie mechanizmu using na więcej niż jednym obiekcie IDisposable.

Kolejnym ciekawy rozszerzenie dla RX jest mechanizm Distinct, który wyśle powiadomienie tylko wtedy, gdy jest ono różne od poprzedniego.

   1:  var texts = Observable
   2:                .FromEventPattern<EventArgs>(textbox, "TextChanged")
   3:                .Select(e => (e.Sender as TextBox).Text)
   4:                .Do(e => Console.WriteLine("Before DistinctUntilChanged: {0}", e))
   5:                .DistinctUntilChanged();

Cały kod jest taki sam jak poprzednio, zmienia się tylko definicja źródła danych dla tekstu. Linia 4 pokazywać będzie, że wiadomości są przesyłane – metoda Do. Natomiast linia 5 zatrzyma ich dalszą propagację, gdy będę one takie same jak poprzednio otrzymane. Należy zwrócić uwagę podczas używania DistinctUntilChanged na kolejność jego użycia. Jeśli zostanie wywołany za wcześnie, kolejne eventy mogą nie zostać przesłane. W ramach ćwiczeń można wstawić go przed Select i zobaczyć co się dzieje. W tym przykładzie, pod uwagę brana będzie zawartości kontrolki w textboxie. A więc, gdy zaznaczona zostanie literka i zostanie zastąpiona przez taką samą, obiekt nasłuchujący nie zostanie powiadomiony o takie zmianie.

Następne rozszerzenie dla RX to Throttle, który powoduje że powiadomienia są przesyłane z pewnym opóźnieniem, każda nowa aktualizacja na źródle resetuje ten licznik. Dla przykładu, gdy chcemy udostępnić mechanizm który działa jak słownik z podpowiedziami, aby nie włączać wyszukiwania dla każdej wpisanej literki, można dodać małe opóźnienie rzędu kilkudziesięciu milisekund, które dla użytkownika będzie nie zauważalne, a jego wykorzystanie spowoduje zmniejszenie wykorzystania zasobów, ponieważ ilość generowanych zapytać będzie mniejsza. Poniżej przykład definicji takiego źródła danych:

   1:  var moves = Observable
   2:            .FromEventPattern<MouseEventArgs>(form, "MouseMove")
   3:            .Select(e => e.EventArgs.Location)
   4:            .Throttle(TimeSpan.FromMilliseconds(100));
   5:   
   6:  var texts = Observable
   7:            .FromEventPattern<EventArgs>(textbox, "TextChanged")
   8:            .Select(e => (e.Sender as TextBox).Text)
   9:            .Throttle(TimeSpan.FromMilliseconds(500))
  10:            .DistinctUntilChanged();

W pierwszy przypadku, informację o pozycji myszy otrzymamy gdy użytkownik okres około 100 ms nie będzie poruszać myszą. Należy być świadomym, że nie dostaniemy wszystkich wartości pośrednich, tylko ostatnią pozycję myszy. W drugim przypadku, jest dodatkowe założenie, użytkownik nie może zmieniać wartości tekstu przez około 500ms, a dodatkowo wartość musi być inna niż poprzednia – modyfikator DistinctUntilChanged. W bardzo prosty sposób ograniczyć można ilość powiadomień, które obsłużyć musi obiekt obsługujący kontrolkę.

Wcześniej mówiłem o tym, że będzie o pełniejszej asynchroniczności oraz o kontrolkach, czas najwyższy na rozwiązanie tej tajemnicy. Jeśli wiemy, że obsługa zdarzeń będzie wymagała interakcji z kontrolkami (lub kolekcjami – np. ObservableCollection w WPF) wymagającymi synchronizami z głównym wątkiem UI należy wykorzystać odpowiednie rozszerzenie, które umożliwia obserwację zdarzeń wykonywaną na głównym wątku aplikacji – zamieszane? Kod was oświeci:

   1:  static void Main(string[] args)
   2:  {
   3:      TextBox t1 = new TextBox();
   4:      Label l1 = new Label { Left = t1.Width + 20 };
   5:      Form f1 = new Form
   6:      {
   7:          Controls = { t1, l1 }
   8:      };
   9:   
  10:      var source = Observable
  11:          .FromEventPattern(t1, "TextChanged")
  12:          .Throttle(TimeSpan.FromMilliseconds(250))
  13:          .Select(x=>(x.Sender as TextBox).Text)
  14:          .DistinctUntilChanged();
  15:   
  16:      using (source
  17:          .ObserveOn(WindowsFormsSynchronizationContext.Current)
  18:          .Subscribe(x => l1.Text = x))
  19:      {
  20:          Application.Run(f1);
  21:      }
  22:  }

Jak widać, źródło danych działa teraz prawdziwie asynchronicznie (wykorzystanie Throttle), aby móc wykonać kod zdefiniowany w OnNext (linia 18) należy wcześniej powiedzieć że obsługa (obserwacja) ma nastąpić w WindowsFormsSynchronizationContext.Current (dla WPF to będzie Application.Current.Dispatcher lub Dispatcher.Current) – lub w implementacji wykorzystać mechanizm synchronizacji, jak zawsze to zależy od potrzeb implementacji. W takiej sytuacji źródło działa w osobnym wątku, ale powiadomienia i ich obsługa nastąpi w wątku głównym, gdzie można modyfikować UI.

Następny przykład jest trochę dłuższy, pokazuje jak wykorzystać RX w połączeniu z serwisami implementowanymi w WCF, dokładniejsze omówienie poniżej:

   1:  private static void Main()
   2:  {
   3:      var service = new DictServiceSoapClient("DictServiceSoap");
   4:      Func<string, string, string, IObservable<DictionaryWord[]>> match = Observable
   5:          .FromAsyncPattern<string, string, string, DictionaryWord[]>(
   6:              service.BeginMatchInDict, service.EndMatchInDict);
   7:   
   8:      Func<string, IObservable<DictionaryWord[]>> matchInWordNetByPrefix = term => match("wn", term, "prefix");
   9:   
  10:   
  11:      var t1 = new TextBox();
  12:      var f1 = new Form
  13:                   {
  14:                       Controls = {t1}
  15:                   };
  16:   
  17:   
  18:      IObservable<string> textSource = Observable
  19:          .FromEventPattern<EventArgs>(t1, "TextChanged")
  20:          .Throttle(TimeSpan.FromMilliseconds(250))
  21:          .Select(x => ((TextBox) x.Sender).Text)
  22:          .Where(x => x.Length > 0)
  23:          .DistinctUntilChanged();
  24:   
  25:      IDisposable formRequest = textSource
  26:          .Finally(()=>Console.WriteLine("Finalized"))
  27:          .Subscribe(x => matchInWordNetByPrefix(x)
  28:                              .Finally(() => Console.WriteLine("Finalized: {0}", x))
  29:                              .Subscribe(words =>
  30:                                             {
  31:                                                 Console.WriteLine("{0} - {1}", x, words.Count());
  32:                                                 foreach (DictionaryWord w in words)
  33:                                                 {
  34:                                                     Console.Write("{0},", w.Word);
  35:                                                 }
  36:                                                 Console.WriteLine("n*******************************************");
  37:                                             },
  38:                                         ex =>
  39:                                             {
  40:                                                 Console.WriteLine("n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
  41:                                                 Console.WriteLine(ex.Message);
  42:                                                 Console.WriteLine("n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
  43:                                             }));
  44:   
  45:   
  46:      using (formRequest)
  47:      {
  48:          Application.Run(f1);
  49:      }
  50:  }

Zacznijmy od początku:
Linia 3 definiuje nowe proxy do serwisu WCF, nie istotne z punktu widzenia RX. Następnie linia 4 definiuje zmienną typu Func, będzie to delegat przyjmujący jako dane wejściowe trzy stringi, a zwracający tablicę wyrazów słownikowych (ach ten mój angielski). Dalej (ciągle w linii 4) przypisujemy do tego delegata metodę z RX wykorzystując wcześniej zdeklarowany serwis WCF, a dokładniej dwie jego metody BeginMatchInDict oraz EndMatchInDict. Są to dwie metody, które umożliwiają asynchroniczne wykorzystanie serwisu. Podczas definiowania dostępu do serwisu WCF zaznaczona została opcja generowania zapytań asynchronicznych. Dalej, linia 8 to ponowne zdefiniowanie delegata, tym razem będzie on przyjmować tylko jeden argument – string, który będzie zawierać właściwe dla nas zapytanie, zwracany typ nie ulegnie zmianie. Pozostałe dwa stringi zostają przypisane na stałe. Ich wartość nie jest istotna dla RX. Teraz możemy korzystać asynchronicznie z WCF podając tylko jednego stringa podczas zapytania. Potem “normalna” definicja formatki, później źródełko z danymi;  pierwsze to textbox, drugie to serwis WCF który na podstawie wysłanej części słowa, zwraca tablicę wyrazów, który mogą je dokończyć (np. “yellow a“: yellow adder’s tongue,yellow ageratum,yellow asphodel,yellow avens). Metoda Finally posłuży aby wykonać jakiś kod w momencie zwalniania zasobów zwróconych przez Subscribe. Kod z linijki 28 zostanie wykonany po tym, gdy wszystkie pasujące słowa zostaną wypisane na ekran. Linijka 26 zostanie wywołana po zamknięciu formatki, czyli  zakończeniu using z linii 46.

Jeśli podczas zabawy z tym przykładem zauważycie, że możliwe jest zdefiniowanie nowego pytania do serwisu WCF zanim, przyjdzie odpowiedź na wcześniejsze – to macie racje (za .net rocks – golfclap for you). Występuje tu taka sytuacja. Na rozwiązanie tego problemu zaproponowano dwa podejścia, dla mnie Switch jest (będzie poniżej) czytelniejszy i zrozumiały. W udostępnionym projekcie są oba rozwiązania.

   1:  static void Main(string[] args)
   2:  {
   3:      var t1 = new TextBox();
   4:      var l1 = new ListBox { Top = t1.Height + 10, Height = 250, Width = 150 };
   5:      var f1 = new Form
   6:      {
   7:          Controls = { t1, l1 }
   8:      };
   9:   
  10:      var textSource = Observable
  11:          .FromEventPattern<EventArgs>(t1, "TextChanged")
  12:          .Throttle(TimeSpan.FromMilliseconds(50))
  13:          .Select(x => (x.Sender as TextBox).Text)
  14:          .Where(x => x.Length >= 3)
  15:          .DistinctUntilChanged()
  16:          .Do(Console.WriteLine);
  17:   
  18:      var service = new DictServiceSoapClient("DictServiceSoap");
  19:      var dictSource = Observable
  20:          .FromAsyncPattern<string, string, string, DictionaryWord[]>(service.BeginMatchInDict, service.EndMatchInDict);
  21:   
  22:      Func<string, IObservable<DictionaryWord[]>> matchInWordNetByPrefix = term => dictSource("wn", term, "prefix");
  23:   
  24:      var data = textSource
  25:          .Select(x => matchInWordNetByPrefix(x))
  26:          .Switch();
  27:   
  28:      using (data
  29:          .ObserveOn(WindowsFormsSynchronizationContext.Current)
  30:          .Subscribe(w =>
  31:          {
  32:              l1.Items.Clear();
  33:              l1.Items.AddRange(w.Select(word => word.Word).ToArray());
  34:          },
  35:          ex =>
  36:          {
  37:              MessageBox.Show(ex.Message);
  38:          }))
  39:   
  40:          Application.Run(f1);
  41:  }

Początek bez zmian, dopiero podczas wykorzystywania tego co wpisze użytkownik i przesyłania tego do serwisu WCF, pojawiają się pierwsze zmiany. Na początku (linia 24) czekamy aż, użytkownik coś wpisze, a następnie wysyłamy to do serwisu. Jeśli w między czasie użytkownik zmieni coś w kontrolce, ponownie wyślemy zapytanie, ale z nową wartością. Aby nie obsługiwać wyników dla poprzedniego zapytania wykorzystana zostaje metoda Switch, której działanie polega na zwróceniu wyniku tylko z ostatniego zapytania, wszystkie poprzednie zostają anulowane. Reszta zmian to tylko kosmetyka, w tym przykładnie wyniki pojawiać się będą w kontrolce na formatce.

Na moje oko RX wydaje się być interesującym rozszerzeniem, umożliwiającym uproszczenie i kodu, a już na pewno metody jak Throttle czy DistinctUntilChanged uczynią aplikacje mniej zasobożernymi. Jak z każdą nowo poznaną technologią trzeba trochę czasu, aby poznać wszystkie za i przeciw. Ja na razie jestem na etapie wow. Poprawcie mnie jeśli się mylę, ale RX jest wyczesany.

Projekt, źródła i książka dostępne pod następującym adresem:
Projekt: https://bitbucket.org/jstadnicki/rx-examples
Git: https://bitbucket.org/jstadnicki/rx-examples.git

PRISM – materiały do nauki.

Jestem zafascynowany framworkiem PRISM, czytam, oglądam i piszę jakieś przykładowe aplikacje z wykorzystaniem tej biblioteki. Prism ułatwia tworzenia modularnych i dynamicznych systemów, dostarcza mechanizm do rozwiązywania zależności (Unity Container), umożliwia dynamiczną zmianę zachowania aplikacji poprzez wczytywanie dodatkowych modułów z katalogu lub aktualizację pliku konfiguracyjnego aplikacji – to tylko część z błyszczących bajerów.
Prism wspiera Silverlight, WPF czy Windows Phone 7 oraz mocno promuje wzorzec MVVM. Aby skorzystać z Prism wystarczy zaciągnąć paczkę z codeplex.com, rozpakować oraz dodać referencje do projektu.

Polecam dwa źródła do nauki: dla posiadających dostęp do płatnych szkoleń na Pluralsight taki oto kurs – Introduction to PRISM . Jeśli nie masz dostępu, nie masz się czym martwić na Channel9 jest inny równie dobry, oraz dwu i pół godzinny film akcji, gdzie autor tworzy prostą aplikację do odbierania i wysyłania poczty elektronicznej – Prism & Silverlight. Pierwszy z materiałów jest oparty na WPF, drugi to aplikacja webowa pisana w Silverlight.

Moje proste działania można obserwować na bitbucket.org pod adresem:
https://bitbucket.org/jstadnicki/prismcapture oraz link do repozytorium git https://jstadnicki@bitbucket.org/jstadnicki/prismcapture.git

Async i Await w Windows8 – małe szoł

Jestem w miarę świeżo po przeczytanie Programowania Windows 8  (w wersji preview) napisanej przez pana, który się nazywa Charles Petzold. Krótka recenzja:
Aktualnie książka zawiera siedem rozdziałów, w nich omówienie XAML, mechanizmu wiązań, kontrolek, layout i krótko o WinRT. Jeśli ktoś z was pisał już w WPF czy SL nie znajdzie w niej (przypominam że mówię ciągle o wersji preview) nic ciekawego. Prawie nic, otóż są dwie nowości warte uwagi wprowadzone w nowszej wersji .NET. CallerMemberName oraz async/await. Książkę kupiłem za 10USD i gdy CP dopisze kolejne rozdziały książki ja je dostanę za darmo. Taki bajer, także warto było wydać 30 parę złoty. Książkę nadal można kupić, choć wciąż jest to wersja preview, ale teraz kosztuje już 20USD (promocja z dosłaniem brakujących rozdziałów nadal obowiązuje). Docelowo finalna wersja ma kosztować 50USD (ciągle pisze usb zamiast usd) i być dostępna w okolicach listopada.

Dzisiaj krótko o tym jak mechanizm async/await jest w stanie uprościć kod pisany z myślą o Windows 8 i aplikacjach w stylu metro.
Zaczniemy od wersji nie zawierającej słów kluczowych async i await. Przykładowa implementacja kodu odpowiedzialnego za wczytanie tekstu ze wskazanego przez użytkownika pliku tekstowego, a zawartości tego pliku wyświetlona w kontrolce na oknie aplikacji. Kod zaczerpnięty z wcześniej przytoczonej książki.

Wybieraj mądrze użytkowniku:
Umożliwiamy wybranie pliku, którego zawartość ma zostać wczytana. Warto zauważyć że filtertype jest w postaci “.txt” bez gwiazdki na początku. Następnie ustawiamy wskaźnik do metody, która ma się wykonać gdy użytkownik potwierdzi swój wybór.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  private void OnOpenAppBarButtonClick(object s, RoutedEventArgs a)
   2:  {
   3:      FileOpenPicker fop = new FileOpenPicker();
   4:      fop.FileTypeFilter.Add(".txt");
   5:      IAsyncOperation<StorageFile> operation = fop.PickSingleFileAsync();
   6:      operation.Completed = OnPickSingleCompleted;
   7:  }

Otwieraj pliki cudny systemie:
Powiadomienie o zakończeniu działania wybieracza plików (FilePicker), warto sprawdzić czy użytkownik i system zachował się fair i dostarczył wszystkich potrzebnych danych wymaganych do załadowania pliku testowego. Jeśli tak to grzecznie prosimy system żeby otworzył plik i dał znać gdy skończy.

   1:  private void OnPickSingleCompleted(IAsyncOperation<StorageFile> asyncInfo, AsyncStatus asyncStatus)
   2:  {
   3:      if (asyncInfo.ErrorCode != null)
   4:      {
   5:          return;
   6:      }
   7:   
   8:      StorageFile storage = asyncInfo.GetResults();
   9:   
  10:      if (storage == null)
  11:      {
  12:          return;
  13:      }
  14:   
  15:      IAsyncOperation<IRandomAccessStreamWithContentType> operation = storage.OpenReadAsync();
  16:      operation.Completed = OnFileOpenReadCompleted;
  17:  }

Czytaj wszystko:
DataReader został w tym przypadku zdefiniowany w klasie. Pamiętamy, że to tylko przykład, nie czepiamy się czystości kodu. Ołkiej dalej, jeśli plik udało się otworzyć zaczynamy jego odczyt. Aby nie marnować czasu czekając na wynik takiej operacji, podobnie jak poprzednio ustawiamy obsługiwacza, który zostanie wywołany po tym, gdy datareader wczyta wszystko z pliku.

   1:  DataReader dataReader;
   2:   
   3:  private void OnFileOpenReadCompleted(IAsyncOperation<IRandomAccessStreamWithContentType> asyncInfo, AsyncStatus asyncStatus)
   4:  {
   5:      if (asyncInfo.ErrorCode != null)
   6:      {
   7:          return;
   8:      }
   9:   
  10:      using (var stream = asyncInfo.GetResults())
  11:      {
  12:          using (this.dataReader = new DataReader(stream))
  13:          {
  14:              uint length = (uint)stream.Size;
  15:              DataReaderLoadOperation operation = this.dataReader.LoadAsync(length);
  16:              operation.Completed = OnDataReaderLoadCompleted;
  17:          }
  18:      }
  19:  }

Wyświetlaj cudowna kontrolko.
Jeśli wszystko się udało, wyciągamy wczytane dane z datareader, a następnie ustawiamy je w kontrolce. Oczywiście, że pamiętamy o tym, że wolno modyfikować ją tylko z głównego wątku. Tutaj też mała zmiana, znika metoda Invoke, należy skorzystać z RunAsync.

   1:  private void OnDataReaderLoadCompleted(IAsyncOperation<uint> asyncInfo, AsyncStatus asyncStatus)
   2:  {
   3:      if (asyncInfo.ErrorCode != null)
   4:      {
   5:          return;
   6:      }
   7:   
   8:      uint length = asyncInfo.GetResults();
   9:      string text = dataReader.ReadString(length);
  10:   
  11:      this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
  12:      {
  13:          this.textblock.Text = text;
  14:      });
  15:  }

No i to tyle. Kod można oczywiście skrócić wykorzystując wyrażenia lambda, ale wtedy nie uzyskamy efektu wow. Racja?

Uwaga, tylko u mnie, tylko teraz, tylko tu,  rewolucja w programowaniu. Jak się to robi w Windows8?

Klik w guzik:
Przypominam aby zwrócić uwagę na słówko kluczowe async podczas definiowania handlera. Async musi być w metodach, które chcą w swoim ciele skorzystać z opcji await. I co ważne brak wykorzystania Dispatcher, ponieważ to co następuje po await jest wywołane w głównym wątku aplikacji.

   1:  private async void OnOpenAppBarButtonClick(object s, RoutedEventArgs a)
   2:  {
   3:      this.textblock.Text = await this.ReadFileAsync();
   4:  }

Niech się dzieje magia.
Właściwa implementacja:

   1:  private async Task<string> ReadFileAsync()
   2:  {
   3:      FileOpenPicker picker = new FileOpenPicker();
   4:      picker.FileTypeFilter.Add(".txt");
   5:      var storage = await picker.PickSingleFileAsync();
   6:      if (storage == null)
   7:      {
   8:          return null;
   9:      }
  10:      return await FileIO.ReadTextAsync(storage);
  11:  }

No ja was proszę, kto teraz nie rzuca bielizną zrywaną przez głowę? Jest efekt wow? Nie ma? Przewińcie na górę i jeszcze raz przeczytajcie ten rozdmuchany kod, a potem z powrotem do nowego z async/await. Jeszcze raz na górę, jeszcze raz na dół, teraz spójrz na mnie, siedzę na krześle.

Ok, zen, wracamy do kodu. Tak napisany kod robi się czytelniejszy, jasne że za tą całą magią robi się syfek, jeśli ktoś oglądał sesje o tym co kompilator C# robi z kodem, to wie co może się znaleźć po sparsowaniu przejrzystego kodu, ale to już nie nasze zmartwienie. My – zwykli programiści, nie musimy się aż tak bardzo przejmować tym co się dzieje za kurtyną. Że MS robi totalną sieczkę z tych kilku linijek kodu, to sprawa MS jak to zaimplementował. Dla mnie (większości z was też to dotyczy) ważne jest, że działa i że mogę tego używać.

To tyle na dziś, zapraszam do dyskusji, uczestnictwa w grupach społecznościowych, np. takiej wrocławskiej i OWIONIE.
Uwagi w komentarzach chętnie przyjmę.
Kod z przykładami do Windows8 z książki CP trzymam na bitbucket. Jeśli ktoś chce obczaić to zapraszam:

git clone https://bitbucket.org/jstadnicki/programming-windows8.git
git clone git@bitbucket.org:jstadnicki/programming-windows8.git

Konfiguracja nhibernate

Nie taki straszny ten nhibernate jak się początkowo wydaje.
Wiem bo sam sprawdziłem, na początku myślałem, znowu wszystko w xml, nigdy nie wiadomo co i gdzie wpisać, … Nie tym razem, wystarczy obejrzeć sobie ten prosty wstęp i okazuje się, że podłączenie nh do lokalnego pliku z sqlce jest proste.
Chcesz korzystać z postgresql jako bazy danych, nic skomplikowanego. Zacznij od ściągnięcia paczki nhibernate. Następnie rozpakuj na dysk, a w środku znajdź katalog o nazwie “Configuration_Templates”, w nim są przykładowe pliku konfiguracyjne dla różnych baz, które są wspierane przez NH, w tym także postgresql.
Dla pełnego działania nh oraz psql wymagane jest także posiadanie npgsql – data provider.
Można zaciągnąć go z oficjalnej strony lub wykorzystując Application Stack Builder z paczki instalacyjnej do PostgreSQL. (Swoją drogą pewnie za pomocą nugeta też się da)
Należy wybrać wersje (jeśli mamy zainstalowanych kilka) psql a następnie driver, który nas interesuje:

Jak widać ja już mam zainstalowane to czego potrzebowałem. Teraz wystarczy już tylko dodać referencję do .dll i można działać.

Jeszcze tylko żeby zapamiętać, pliku konfiguracyjne dla nhibernate oznaczyłem jako content oraz copy always. Natomiast pliki odpowiadające za mapowanie obiektów jako embedded resource.
W moim przypadku konfiguracja zawiera dane użytkownika oraz hasło i nie może być w taki sposób wykorzystane w profesjonalnym rozwiązaniu, ale na potrzeby nauki oraz czytelności kodu jest jak jest.

Ostatnie żeby pamiętać, nh dostarcza schemat dla xml, aby mieć podpowiadanie składni w VS należy dodać pliki .xsd do projektu, albo wrzucić je do “X:PROGRAM_FILESMicrosoft Visual Studio VERSIONXmlSchemas”. Odpowiednio podmieniając X, PROGRAM_FILES oraz VERSION 😉

Jeśli ktoś chce może skorzystać z moich małych projektów, stworzonych na podstawie tutoriala z początku wpisu. Zawiera on konfiguracja dla mssql oraz pgsql i znajduje się tutaj lub można sklonować to https://bitbucket.org/jstadnicki/nhibernate-examples.git repozytorium.

Więcej nie wiem. Ale się nauczę.

ps.
W zasadzie skoro w tytule jest konfiguracja nhibernate to czemu nie wrzucić jest od razu tutaj:
MSSQL:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <hibernate-configuration 
   3:      xmlns="urn:nhibernate-configuration-2.2">
   4:      <session-factory>
   5:          <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
   6:          <property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
   7:          <property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>
   8:          <property name="connection.connection_string">Data Source=FirstSample.sdf</property>
   9:          <property name="show_sql">true</property>
  10:      </session-factory>    
  11:  </hibernate-configuration>

Linia 9 jest opcjonalna, powoduje że zapytania generowane przez NH są wypluwane na konsole.

PostgreSQL:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <hibernate-configuration
   3:      xmlns="urn:nhibernate-configuration-2.2">
   4:      <session-factory name="NHibernate.Test">
   5:          <property name="connection.driver_class">NHibernate.Driver.NpgsqlDriver</property>
   6:          <property name="connection.connection_string">
   7:              Server=localhost;Port=12345;Database=Products;User ID=postgres;Password=1234567890;
   8:          </property>
   9:          <property name="dialect">NHibernate.Dialect.PostgreSQL82Dialect</property>
  10:          <property name="show_sql">True</property>
  11:      </session-factory>    
  12:  </hibernate-configuration>

Jak widać, w tym przypadku czarno na białawym podane jest hasło oraz nazwa użytkownika do bazy. TAK NIE MOŻE SIĘ DZIAĆ.

Czysty kod

Jestem na krótko po przeczytaniu Czystego Kodu od Wujka Boba i chciałbym się podzielić kilkoma wrażeniami z książki. Jest to zbiór przypowieści o tym, jak pisać kod, pozostał on czysty i czytelny. Aby utrzymanie kodu nie było karą za złe zachowanie, a pisanie testów nie było piekłem dla programistów. Poza tym testy piszemy przed napisaniem kodu – rajt?

Wszystkie złe rzeczy, które przez przypadek mogliśmy wyprodukować są opatrzone przykładowym kodem, rozwiązania także przedstawiają kod. Wskazówki sugerowane przez wujka, są dobre dla początkujących programistów jak i dla wypasionych developerów – nawet jako forma przypomnienia.

Poniżej kilka przykładów, które sobie zanotowałem, część z nich to oczywistość. Uważam jednak, że warto wracać do podstaw, kod pochodzi z książki.
Nazwy zmiennych
Dobrze aby nazwy było tak dobrane, aby dało się je wymówić. Ułatwia to zapamiętywanie struktury kodu, komunikację w projekcie oraz czytelność. Dotyczy to nazw klas/struktur, metod oraz zmiennych. Jak pisze wuj Bob: jeśli piszesz komentarz aby opisać zmienną, usuń komentarz i popraw nazwę zmiennej.
ZŁY KOD DOBRY KOD
class DtaRcrd102 
{
    private DateTime genymdhms;
    private DateTime modymdhms;
    private string pszqint = “102”;
};
class Customer 
{
    private DateTime generationTimestamp;
    private DateTime modificationTimestamp;
    private string recordId = “102”;
};
Magiczne wartości
Omijamy je z daleka, jeśli musisz wpisać gdzieś ‘7’, to utwórz zmienną, która swoją nazwą powie, że ‘7’ to jest dni w tygodniu.
ZŁY KOD DOBRY KOD
for (int j=0; j<34; j++) 
{
    s += (t[j]*4)/5;
}
int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) 
{
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

Tworzenie obiektów
Nie ograniczaj się tylko do konstruktorów i ich ciągłego przeciążenia, skorzystaj z wzorca metoda wytwórcza.
MOŻNA LEPIEJ
Complex fulcrumPoint = new Complex(23.0);

Complex fulcrumPoint = Complex.FromRealNumber(23.0);
Krótko zwięźle i na temat
Ilość przekazywanych argumentów do funckji powinna być możliwie najmniejsza:
1 – spoko
2 – jeśli musisz
3 – zastanów co robisz 
4 – WTF? 
5 – link
Może któryś z parametrów uda się przenieść na stałe do klasy, co zmniejszy ich ilość w sygnaturze funkcji. Jeśli nie, to zaproponuj strukturę przechowującą tą ogromną ilość parametrów i przekazuj taką strukturę.
Pamiętacie wpis o SOLID, otóż wyobraźcie to sobie, że SRP (single
responsibility principle) można też zastosować wobec metod i funkcji.
Niech będę zwięzłe, krótkie, monotematyczne i nie posiadają efektów ubocznych.
ZŁY KOD, ZŁY
public boolean checkPassword(String userName, String password) 
{
    User user = UserGateway.findByName(userName);
    if (user != User.NULL) 
    {
        String codedPhrase = user.getPhraseEncodedByPassword();
        String phrase = cryptographer.decrypt(codedPhrase, password);
        if (“Valid Password”.equals(phrase)) 
        {
            Session.initialize();
            return true;
        }
    }
    return false;
}

Rozdzielenie odpowiedzialności
CQS (command query separation)- Bob zaleca odseparowanie polecenia od zwracania wartości:
STARE NAWYKI NOWE NAWYKI
if (set(“username”, “unclebob”))
{…}
if (attributeExists(“username”))
{

     setAttribute(“username”, “unclebob”);

}

Trochę więcej o CQS można znaleźć na wiki.

Błędy i wyjątki
Preferuj wyjątki zamiast zwracania kodu błędu:
ŹLE DOBRZE
if (deletePage(page) == E_OK) 
{
    if (registry.deleteReference(page.name) == E_OK) 
    {
        if (configKeys.deleteKey(page.name.makeKey()) == E_OK)
        {
            logger.log(“page deleted”);
        }
         else 
        {
            logger.log(“configKey not deleted”);
        }
    } 
    else 
    {
        logger.log(“deleteReference from registry failed”);
    }

else 
{
    logger.log(“delete failed”);
    return E_ERROR;
}
try
{
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey()
}
catch(…)
{
}

Obsługę błędów i wyjątków separuj od logiki kodu:

RAZEM OSOBNO
try 
{
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}
catch (Exception e) 
{
    logger.log(e.getMessage());
}
public void delete(Page page) 
{
    try 
    {
        deletePageAndAllReferences(page);
    }
    catch (Exception e) 
    {
        logError(e);
    }
}

private void deletePageAndAllReferences(Page page) throws Exception 
{
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}

private void logError(Exception e) 
{
    logger.log(e.getMessage());
}

Komentarze
Nie powtarzaj się i nie opisuj kodu w komentarzach:
SAMO ZŁO
assertTrue(a.compareTo(a) == 0); // a == a
assertTrue(a.compareTo(b) != 0); // a != b
assertTrue(ab.compareTo(ab) == 0); // ab == ab
// if the timeout is reached.
public synchronized void waitForClose(final long timeoutMillis) throws Exception
{
    if(!closed)
    {
        wait(timeoutMillis);
        if(!closed)
            throw new Exception(“MockResponseSender could not be closed”);
    }
}

Formatowanie kodu
Ustal formatowanie w projekcie i się go trzymaj, popraw czytelność stosując wcięcia, odstępy.
GORZEJ LEPIEJ
void foo()
{
    int zmiennaA;
    int zmiennaB;
    int zmiennaC;
    operacjaA()
    operacjaB()
    operacjaC()
    operacjaD()
}
void foo()
{
    int zmiennaA;
    int zmiennaB;
    int zmiennaC;

    
    operacjaA()
    operacjaB()

    
    operacjaC()
    operacjaD()
}
Abstrakcja i interfejsy
Udostępniaj tylko tyle informacji o klasie i jej stanie wewnętrznym ile potrzeba. Zasada ograniczonego zaufania w programowaniu też ma sens, korzystaj z interfejsów i abstrakcji
OK OKIEJEJ ( w sensie, że lepiej)
public interface Vehicle 
{
    double getFuelTankCapacityInGallons();
    double getGallonsOfGasoline();
}
public interface Vehicle 
{
    double getPercentFuelRemaining();
}

Zastanów się nad tym co udostępniasz innym:

🙁 🙂
List<int> getList()
{…}
IEnumerable<int> getList()
{…}

Chyba że lubisz (lub chcesz), sytuację gdy klient może wywołać getList().Clear();

Tam sięgaj gdzie ręka dosięga:

Klasy powinny korzystać ze swoich zmiennych, otrzymanych jako parametr, lub stworzonych przez siebie. Jeśli musisz stworzyć  okrutne połączenia, lepiej jest rozbić wszystkie wyniki na poszczególne wywołania. Wyjątkiem jest sytuacja, gdy dostęp następuje przez publiczne pola struktur. Choć i tak najlepiej jest kazać klasie odpowiedzialnej zrobić wszystko za nas:
ZŁE LEPIEJ SUPERAŚNIE
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
final String outputDir = ctxt.options.scratchDir.absolutePath;
output.ctxt.GetAbsolutePathToScratchDir()
//sposób na uratowanie honoru
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();

Law of Demeter

Obiekty NULL
O ile to możliwe zamiast NULL postaraj się zwracać neutralne obiekty, takie które nie wywalą kodu a jednocześnie nie spowodują jego zmiany:
BRZYDKO ŁADNO
List employees = getEmployees();
if (employees != null) {
    for(Employee e : employees) {
        totalPay += e.getPay();
    }
}
List employees = getEmployees();
for(Employee e : employees) {
    totalPay += e.getPay();
}

getEmployess() zamiast NULL zwraca pustą kolekcję.

To tylko część z tego co proponuje Martin. Jeśli jesteście zainteresowani to jeszcze raz zachęcam do przeczytania. Czyta się szybko i przyjemnie. Jedyny minus (taki na siłę) to przykłady są w JAVA – ale niech będzie. 

Uwagi i komentarza zawsze chętnie przyjmę.