NDepend4 – co potrafi statyczna analiza kodu.

Dostałem NDepend4 w zamian za jego opis 🙂 Nie muszę o nim mówić w samych słodkich komentarzach, także można wpis czytać do końca.
NDepend służy to statycznej analizy kodu napisanego w .NET. Potrafi ocenić kod po wieloma względami, np: ze względu na skomplikowanie, ilość linii kod czy instrukcji IL, ilość zmiennych, łatwość modyfikacji, może zasugerować zmianę typów z referencyjnych na wartościowe (ref type –> value type).
Sama instalacja jest banalnie prosta, ściągamy paczkę ze strony, rozpakowujemy oraz wrzucamy plik z licencją :> Chyyyba że jej nie macie, wtedy można skorzystać z darmowej wersji 14 dniowej. To wszystko, potem wystarczy odpalić i załadować plik z solucją, którą chcielibyśmy się bliżej przyjrzeć.

Do opisu wykorzystam swoją pierwszą aplikację na Windows Phone. Trochę wstyd, ale dla dobra ludzkości trzeba się czasem trochę poświęcić. Przy okazji, jest ona dostępna na Baazar (nieoficjalny marketplace dla Windows Phone 7) – nazywa się CompactCal

Po uruchominiu NDepend można załadować plik solucji, którą chcemy poddać analizie. Następnie zostanie wyświetlona lista assemblies, które zostaną przeanalizowane:

Jak widać może się okazać, że czegoś będzie brakować, wtedy można dodać ręcznie brakujące pliki (np. używając drag’n’drop) walnąć OK. Warto zwrócić uwagę z której konfiguracji będą brane: debug czy release.

Czary mary, czekamy chwilę i wybieram opcje Close dialog – chce wszystko sam zobaczyć.
Pierwsze ciary na plecach, bo widzę bałagan w kodzie. Wszytko przez diagram zależności klas, u mnie wygląda on tak:

(Trochę się obrazek rozjechał)
Ci z nas, którzy posiadają Visual Studio wyższą niż expres (i chyba pro też), mogą znać już taki obrazek. Podobny widok można utworzyć w MVSC z menu Architecture -> Generate Dependency Graph -> By Assembly.
Ogólnie rzecz ujmując widać na nim, co od czego zależy i jak bardzo, im grubsza kreska tym więcej zależności w kodzie. Może być taka sytuacja że już tutaj zauważymy zależności których
nie powinno być. Może z jakiegoś powodu nasz View sięga bezpośrednio
do serwisów z danymi, a miał to robić tylko ViewModel. Po rozwiązaniu takich problemów, ja najczęściej usuwam  zależności od systemowy plików, tak jest dla mniej przejrzyściej.
Niestety ND (żeby nie pisać ciągle całej nazwy) nie potrafi sobie poradzić z ułożeniem kwadracików tak, aby linie się nigdzie nie przecinały. Nad czytelnością trzeba diagramu trzeba popracować samodzielnie. Nie wiem czemu, ale lubię właśnie w ten sposób mieć ułożone wszelkie diagramy.
Kolejnym innym widokiem zależności jest Matrix view, jest czytelniejszy niż diagram powyżej, w przypadkach gdy jest bardzo dużo obiektów do analizy.

U mnie tego nie widać, ale gdy pojawią się cyferki na czarnym tle, oznaczać to będzie zależności cykliczne. Choć muszę się przyznać, że gdy po raz pierwszy uruchomiłem ND na tym projekcie pewnie znalazło by się kilka czarnych miejsc. Na szczęście od tamtego czasu zdążyłem poprawić kod, a ten wpis powstaje dużo później. Widać na nim dokładnie zależności modułów, możemy badać zależność na podstawie ilości wykorzystywanych metod, pól, namespaces, typów zmiennych, i jeszcze kilku innych. ND potrafi także wykryć zależności nie bezpośrednie.
Po kliknięciu na jedno ze skrzyżowań, zobaczymy jeszcze dokładniejszy diagram tego, gdzie i jak są wykorzystywane te zależności, np. ViewModel vs. Helpers:

Do tego można wygenerować ciekawszy graph (Export Matrix Code Elements to Graph):

Co w wyniku da coś takiego:

W tym przypadku diagram zależności jest czytelny (linie się nigdzie nie przecinają).

To dopiero początek, jeśli już pogodziliśmy się ze skalą zależności w naszym kodzie warto przejść dalej i zobaczyć jakie reguły złamaliśmy oraz gdzie. ND dostarcza ponad 80 zdefiniowanych reguł. Możemy samodzielnie je modyfikować, dodawać nowe oraz usuwać te z nich , które naszym lub architekta odpowiedzialnego za projekt są z jakiegoś powodu nie potrzebne.

Tak bardzo źle nie jest, brak czerwonych kółeczek.

Jak widać powyżej istnieje kilka różnych grup zasad, z których część została w brzydki sposób naruszona, a część jest akceptowalna. Zobaczmy co zrobiłem źle, sprawdźmy Code Quality (dwuklik i widać szczegóły):

Każda grupa składa się znowu z kilku mniejszych reguł, tutaj jak widać mam najwyraźniej problem ze zbyt dużymi klasami i metodami. Brakiem komentarzy się nie przejmuje. Mój kod komentuje się sam – tak go wytresowałem.

Ważne jest to że parametry definiujące te reguły możemy samodzielnie zmieniać. Dla przykładu domyślne aby metoda została oznaczona za posiadającą zbyt duża ilość parametrów,  musi posiadać ich więcej niż pięć:

Metody zbyt duże muszą mieć więcej niż 30 linii kodu lub więcej niż 200 instrukcji IL.

Gdy już zdecydujemy się obejrzeć listę przewinień służy do tego osobny panel, ja sprawdzę którą z metod tak strasznie spasłem kodem:

Ja mam dwa takie przypadki, gdzie ten drugi jest bliski wartości granicznej. Aby obejrzeć kod wystarczy dwuklik na wybranym przypadku. W wersji darmowej trzeba niestety ręcznie znaleźć kod w MSVC.

.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:  public static WriteableBitmap GenerateBitmapImage(string aHeading, IEnumerable<string> aItems)
   2:  {
   3:      WriteableBitmap wbm = null;
   4:      StackPanel sp = new StackPanel();
   5:      sp.Margin = new Thickness { Left = 6, Bottom = 0, Right = 6, Top = 0 };
   6:      sp.Height = 173;
   7:      sp.Width = 173;
   8:      sp.Background = new SolidColorBrush(Colors.Transparent);
   9:   
  10:      if (!string.IsNullOrEmpty(aHeading))
  11:      {
  12:          TextBlock heading = new TextBlock();
  13:          heading.Style = (Style)Application.Current.Resources["PhoneTextTitle3Style"];
  14:   
  15:          heading.Text = aHeading;
  16:          heading.Height = 35;
  17:          heading.Width = 173;
  18:   
  19:          sp.Children.Add(heading);
  20:      }
  21:   
  22:      int maxitems = (string.IsNullOrEmpty(aHeading)) ? 5 : 6;
  23:      int c = 0;
  24:   
  25:      foreach (var item in aItems)
  26:      {
  27:          TextBlock tbAction = new TextBlock();
  28:          tbAction.Foreground = (SolidColorBrush)Application.Current.Resources["PhoneAccentBrush"];
  29:          tbAction.Text = item;
  30:          tbAction.Style = (Style)Application.Current.Resources["PhoneTextNormalStyle"];
  31:          tbAction.Margin = new Thickness(4, 0, 0, 0);
  32:          tbAction.Height = 27;
  33:          tbAction.Width = 173;
  34:          sp.Children.Add(tbAction);
  35:          if (c++ > maxitems)
  36:          {
  37:              break;
  38:          }
  39:      }
  40:   
  41:      //call measure, arrange and updatelayout to prepare for rendering
  42:      sp.Measure(new Size(173, 173));
  43:      sp.Arrange(new Rect(0, 0, 173, 173));
  44:      sp.UpdateLayout();
  45:   
  46:      wbm = new WriteableBitmap(173, 173);
  47:      wbm.Render(sp, null);
  48:      wbm.Invalidate();
  49:      return wbm;
  50:  }

Rzeczywiście całkiem sporo, kod z bloków IF oraz FOREACH można wyrzucić do osobnych metod, tak samo ostatnie linijki z kodem który wymusza render na bitmapie. Poprawię to później 😉
To tylko jeden z wielu przypadków, który jest możliwy do przeanalizowania w kodzie. Jak już napisałem ND domyślnie ma ich ponad 80. Na stronie domowej są przykłady jak pisać własne CQL (code query language) i analizować kod na inny sposób, np:

  • Ostrzegaj gdy code coverage poniżej n%
  • Nazwy interfejsów nie zaczynające się od litery I
  • Wykorzystanie wątków inaczej niż przez ThreadPool
  • Zmiany wartości zmiennych, bez wykorzystania mechanizmów synchronizacji
  • Zbyt długa lista typów dziedziczących po X

Oprócz tego co wymieniłem powyżej, generowany jest także dodatkowy raport podsumowujący cały projekt, wykresy, liczby, inne pierdółki, coś co menadżerowie i klienci lubią najbardziej.
Skupmy się na najważniejszej grupie, na programistach.Są tam także informacje, które nam także się przydadzą.
Dodatkowy wykres wyświetlający stopień abstrakcji oraz stabilność waszego kodu. Gdzie stabilność nie oznacza dobrego kodu, a raczej współpracę z innymi typami (wsparcie dla polimorfizmu). W zasadzie ciężko wytłumaczyć ten parametr. Scott Hanselman ma podobny wpis o ND, tam tłumaczy trochę lepiej ten wykres: post Scotta.
Teraz będzie siara, bo o ile jestem zwolennikiem abstrakcji, to w tym projekcie pojechałem bez niej:

Warto trzymać się zielonej strefy.
I tak Service oraz GeocodeService był już tworzony z myślą o testach czy zastosowaniem innego źródła danych, dlatego znajduje się wyżej jeśli chodzi o oś Y (abstrakcję), natomiast Model, Extension (tam są metody rozszerzające), Helpery, etc jest płaskie jak deska. Położenie na osi X działa w taki sposób: jeśli assembly jest nie rozszerzalne, brakuje mu/jej jakiejkolwiek wirtualności, a dodatkowo wiele innych typów od niej zależy tym bliżej strefy bólu (na lewo) będzie się znajdować. U mnie wygląda to tak, że kod nie jest abstrakcyjny (są momenty), jednak nie posiadam klas które są wykorzystywane przez wszystkie inne. Brak tutaj typowych “utilsów” czy klas statycznych, do których wszyscy sięgają. Może dzięki temu jestem z pięknej zielonej strefie szczęścia. Model mógłby dodać od siebie trochę abstrakcji (może ISP), mógłbym sprawdzić czy wszędzie gdzie jest on wykorzystywany jest to potrzebne, może rozbić część klas na trochę mniejsze. Natomiast patrząc na Service widać że zbliża się on do powoli do zbyt wielkiej abstrakcji, lub braku wykorzystania części jego funkcjonalności. Warto sprawdzić czy wszystkie jego metody są potrzebne, może da się część z nich tak zmienić, aby wykorzystywały jakiś wspólny kod.

Z dodatkowych w raporcie ND można zobaczyć ile jest w sumie klas, metod w klasach, właściwości w klasach, linii kodu w projekcie, klasach, metodach, wszystko to co wymieniłem można także badać pod względem ilości poleceń w IL.

Po co utrzymywać kod z którego nikt nie korzysta? W moim przypadku zostało kilka metod, które zostały domyślnie stworzone przez wizarda podczas tworzenia projektu na Windows Phone, np. cała seria ApplicationLaunching, ApplicationActivated, ApplicationDeactivated czy ApplicationClosing. z której nigdzie nie korzystam. Takie proste rzeczy, a czynią kod z którym się pracuje przyjemniejszym w utrzymaniu.

ND wspiera MSVC 2008, 2010 oraz 2012. Umożliwia badanie kodu prosto z menu kontekstowego w edytorze:
Raporty można zapisywać, a następnie porównywać czy zmiany wprowadzane idą ku lepszemu. Dzięki integracji z VS można także prosto edytować i uruchamiać zapytania CQL – prosto ze środowiska pracy.
Na krótkich filmikach widziałem także, że ND potrafi porównywać ze sobą poszczególne wersje aplikacji. Niestety nie wiem jak do tego dojść, podejrzewam, że trzeba pilnować numerów wersji w assembly.
Odpalany z linii poleceń powinien być prosty do integracji z system CI (w domu takiego czegoś nie posiadam) i generować odpowiednio konfigurowalne raporty – menadżerowie się ucieszą. Możliwość zdefiniowania własnych reguł przy pomocy CQL umożliwi generowanie ostrzeżeń podczas budowania kodu, który np. nie posiada testów.

Pisałem wcześniej o tym, że warto rozmawiać. Zadawać pytania i czytać kod nie tylko swój. Poprosić znajomego o sprawdzenie tego co stworzyliśmy. ND wydaje się być czymś co może ten proces częściowo zastąpić. Nie warto jednak od razu usuwać ludzi z listy kontaktów. Jasna sprawa, że dla mnie czy innego Kowalskiego (pozdrawiam Tomka jeśli to czyta) jest on raczej drogim narzędziem 299 euro, szczególnie gdy w domu piszemy kod do szuflady. Ale gdy zarabia się na oprogramowaniu to świetne rozwiązanie, senior czy inny architekt może na początku projektu ustalić pewne zasady, który kod musi spełniać i dopiero gdy czekin w repozytorium spełni je wszystkie, zostanie przesłany do osobistego review. Pozwali to zaoszczędzić wszystkim dużo czasu (czas to pieniądz) oraz pominąć głupie i często powtarzające się błędy, np. brak słowa sealed przy klasie nie przeznaczonej do dziedziczenia.

TL;DR; (podsumowanie)
NDepend to dobre narzędzie do statycznego sprawdzenia kodu. Szczególnie przydatne w komercyjnych projektach, pozwoli automatycznie pilnować porządku w kodzie, zapewni przestrzeganie przyjętych zasad w projekcie. Zadowoli menadżerów wymagających nudnej papierologii. Nawet jeśli piszesz do szuflady, to na koniec lub po zakończeniu projektu, wykorzystaj wersję 14 dniową i sprawdź swój kod. Może się okazać, że da się lepiej.
Jeśli miałbym narzekać, to mogliby dać wersję darmową do domowego użytku.

Jest także NDepened dla Javy oraz dla C++. Niestety nie wiem jak dobre/złe są.

Linki:
NDepend strona domowa: http://www.ndepend.com/Default.aspx
Cennik: http://www.ndepend.com/Purchase.aspx
Lista ficzerów, część z nich posiada filmiki. Zabawne jest to, że korzystają z syntezatora mowy: http://www.ndepend.com/Features.aspx

Jeśli macie pytania to proszę o kontakt, jeśli chcecie żebym opisał jakąś część funkcjonalności ND to piszcie. Jeśli chcecie abym sprawdził jakiś kod to piszcie.

Metody rozszerzające – testowanie i porządek w api.

Jak każdemu porządnemu developerowi zdarza mi się czasem napisać testy. Jak każdemu porządnemu developerowi, czasem zdarza mi się wykorzystać mechanizm metod rozszerzających (jeśli nie wiesz o czym mówię sprawdź na msdn). Jak każdy prawdziwy developer, chciałem przetestować logikę, która była wykorzystywana w jednej z takich metod. W zasadzie to nie w samej metodzie, chciałem sprawdzić czy zostanie wywołana z wartościami, które są dla mnie ważne.
Zacznę od metod rozszerzających, a testowanie przyjdzie samo.

Tak wygląda interfejs udostępniony przez samą (bez metod rozszerzających) klasę List:

Aby przewinąć listę na sam dół wystarczą trzy wciśnięcia Page Down. Jak widać jedyny import, z którego korzystam to System.Collections.Generic, wymagany aby móc skorzystać z List

To się stanie gdy dodam System.Linq: (obrazek się troszkę nie mieści)

Pojawi się nowa funkcjonalność, zdefiniowana w System.Linq, która pozwala robić cuda z listą i innymi IEnumerable (jak również innymi obiektami). Po imporcie aby przewinąć listę metod, trzeba osiem razy walnąć w Page Down.

Co zrobić, aby lista nowych funkcjonalności nie zaśmiecała dostępnego api? Można to wszystko ukryć przy pomocy jednej klasy, która przekierowuje żądania dalej, do właściwej części, gdzie zdefiniowane będą rozszerzenia
Normalnie implementacja rozszerzeń może wyglądać jakoś tak:

.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:  namespace ConsoleApplication
   2:  {
   3:   
   4:      class Program
   5:      {
   6:          static void Main(string[] args)
   7:          {
   8:              MyClass mc = new MyClass();
   9:          }
  10:      }
  11:   
  12:      public static class MyClassExtension
  13:      {
  14:          public static void Foo0(this MyClass target) { }
  15:          public static void Foo1(this MyClass target) { }
  16:          public static void Foo2(this MyClass target) { }
  17:          public static void Foo3(this MyClass target) { }
  18:          public static void Foo4(this MyClass target) { }
  19:          public static void Foo5(this MyClass target) { }
  20:          public static void Foo6(this MyClass target) { }
  21:          public static void Foo7(this MyClass target) { }
  22:          public static void Foo8(this MyClass target) { }
  23:          public static void Foo9(this MyClass target) { }
  24:      }
  25:   
  26:      public class MyClass
  27:      {
  28:      }
  29:  }

Po takiej implementacji otrzymujemy cztery metody podstawowe, oraz dodatkowy dziesięć nowych z rozszerzenia w MyClassExtension.

Można trochę to wszystko uprościć dodając odpowiednią warstwę abstrakcji (abstrakcja was uwolni, zapamiętajcie to). Opis poniżej:

   1:  namespace ConsoleApplication
   2:  {
   3:      using System;
   4:   
   5:      class Program
   6:      {
   7:          static void Main(string[] args)
   8:          {
   9:              MyClass mc = new MyClass();
  10:              mc.ExtensionService().Foo0();
  11:          }
  12:      }
  13:   
  14:      public static class MyClassExtension
  15:      {
  16:          public static Func<MyClass, IMyClassExtensionService> ExtensionFactory { get; set; }
  17:   
  18:          static MyClassExtension()
  19:          {
  20:              ExtensionFactory = target=> new MyClassExtensionReleaseVersion(target);
  21:          }
  22:   
  23:          public static IMyClassExtensionService ExtensionService(this MyClass target)
  24:          {
  25:              return ExtensionFactory(target);
  26:          }
  27:      }
  28:   
  29:      public interface IMyClassExtensionService
  30:      {
  31:          void Foo0();
  32:          void Foo1();
  33:          void Foo2();
  34:          void Foo3();
  35:          void Foo4();
  36:          void Foo5();
  37:          void Foo6();
  38:          void Foo7();
  39:          void Foo8();
  40:          void Foo9();
  41:      }
  42:   
  43:      public class MyClassExtensionReleaseVersion : IMyClassExtensionService
  44:      {
  45:          private MyClass myclass;
  46:   
  47:          public MyClassExtensionReleaseVersion(MyClass target)
  48:          {
  49:              this.myclass = target;
  50:          }
  51:   
  52:          public void Foo0() { }
  53:          public void Foo1() { }
  54:          public void Foo2() { }
  55:          public void Foo3() { }
  56:          public void Foo4() { }
  57:          public void Foo5() { }
  58:          public void Foo6() { }
  59:          public void Foo7() { }
  60:          public void Foo8() { }
  61:          public void Foo9() { }
  62:      }
  63:   
  64:      public class MyClass
  65:      {
  66:      }
  67:  }

Tak się to prezentuje teraz:

Zacznę od linijki 29 gdzie zdefiniowany został interfejs ze wszystkimi metodami, które mają działać jako rozszerzenie dla klasy MyClass. Linia 43 to implementacja tego interfejsu, warto zauważyć że klasa ta nie jest statyczna, oraz nie posiada statycznych pól, a metody nie przyjmują parametrów. W konstruktorze otrzymuje ona obiekt, na rzecz którego ma działać. Najlepsze zostawiam na koniec, linijka 14, klasa udostępniająca rozszerzenie dla MyClass. W ExtensionService (23) wykorzystywana jest właściwość ExtensionFactory, który jest zwyczajną metodą wytwórczą, zwracającą nowy obiekt MyClassExtensionReleaseVersion, do którego przesyła obiekt, na rzecz którego ma zostać wywołana metoda rozszerzająca. Mam nadzieję, że kod tłumaczy to prościej niż ja. Linia 10 to nowy sposób na wywołanie metod rozszerzających. 
Właściwość ExtensionFactory została zdefiniowana jako publiczna, co umożliwia jego zmianę w razie potrzeby testowania. Tak samo obiekt rozszerzający jest deklarowany poprzez interfejs, w związku z czym w testach można wykorzystać np. MyClassExtensionMockVersion i sprawdzać czy klasa zachowuje się tak jak tego oczekujemy.

Ja skorzystałem z tego rozwiązania podczas testowania nawigacji w projekcie, który korzysta z PRISMa, tam do zmiany widoków wykorzystywany jest interfejs IRegionManager i kilka metod rozszerzających ten interfejs. Chciałem sprawdzić, czy po wykonaniu operacji X jedna z moich klas zarząda zmiany widoku. Jedynym sposobem, na sprawdzenie parametrów przesłanych w query było stworzenie i podstawienie własnej implementacji metod rozszerzających. Na początku testów mam taki zapis:

   1:  Capture.Common.Prism.PrismExtensions.ServiceFactory = p => new NavigationServiceMock(p);

Tak wygląda mój lipny serwis, który działa zamiast tego z Prism:

   1:  public class NavigationServiceMock : INavigation
   2:  {
   3:      private IRegionManager p;
   4:   
   5:      public NavigationServiceMock(IRegionManager p)
   6:      {
   7:          this.p = p;
   8:      }
   9:   
  10:      public IRegionManager AddToRegion(string regionName, object view)
  11:      {
  12:          throw new NotImplementedException();
  13:      }
  14:   
  15:      public IRegionManager RegisterViewWithRegion(string regionName, Func<object> getContentDelegate)
  16:      {
  17:          throw new NotImplementedException();
  18:      }
  19:   
  20:      public IRegionManager RegisterViewWithRegion(string regionName, Type viewType)
  21:      {
  22:          throw new NotImplementedException();
  23:      }
  24:   
  25:      public void RequestNavigate(string regionName, string source)
  26:      {
  27:          throw new NotImplementedException();
  28:      }
  29:   
  30:      public void RequestNavigate(string regionName, Uri source)
  31:      {
  32:          LoadTradeViewModelTest.NavigationServiceMock_RequestedNavigatedTargetRegionName = regionName;
  33:          LoadTradeViewModelTest.NavigationServiceMock_ReqestedNavigatedTargetUri = source.OriginalString;
  34:      }
  35:   
  36:      public void RequestNavigate(string regionName, string source, Action<NavigationResult> navigationCallback)
  37:      {
  38:          throw new NotImplementedException();
  39:      }
  40:   
  41:      public void RequestNavigate(string regionName, Uri source, Action<NavigationResult> navigationCallback)
  42:      {
  43:          throw new NotImplementedException();
  44:      }
  45:  }

Najważniejsze w tym wszystkim to RequestNavigate (30) gdzie zapisuje żądany region i uri. Później w teście porównuje czy są takie jak oczekiwałem. Wszystkie NotImplementedException są nie używane w testach, dlatego też mogły pozostać w takiej domyślnie oferowanej przez VS formie.
Normalna implementacja przekazuje wszystkie parametry do oryginalnej implementacji.

Pojawia się pewien problem, przy próbie stworzenie podobnego rozwiązania dla klas generycznych. Ten problem to kompilator, który cały czas twierdzi że metody rozszerzające muszą być w klasach statycznych, nie generycznych. Same metody mogą być generyczne. Aby to obejść na szybko wymyśliłem coś takiego

   1:  namespace ConsoleApplication
   2:  {
   3:      using System;
   4:      using System.Collections.Generic;
   5:   
   6:      class Program
   7:      {
   8:          static void Main(string[] args)
   9:          {
  10:              List<int> l = new List<int>();
  11:              l.GetExtension().Foo1();
  12:          }
  13:      }
  14:   
  15:      public static class ExtensionsFactory<T>
  16:      {
  17:          public static Func<List<T>, IListExtensionService> Factory { get; set; }
  18:      }
  19:   
  20:      public static class ListExtension
  21:      {
  22:          public static IListExtensionService GetExtension<T>(this List<T> target)
  23:          {
  24:              if (ExtensionsFactory<T>.Factory == null)
  25:              {
  26:                  ExtensionsFactory<T>.Factory = o => new ListExtensionSerivce<T>(o);
  27:              }
  28:   
  29:              return ExtensionsFactory<T>.Factory(target);
  30:          }
  31:      }
  32:   
  33:      public interface IListExtensionService
  34:      {
  35:          void Foo1();
  36:      }
  37:   
  38:      public class ListExtensionSerivce<T> : IListExtensionService
  39:      {
  40:          private List<T> list;
  41:   
  42:          public ListExtensionSerivce(List<T> target)
  43:          {
  44:              this.list = target;
  45:          }
  46:   
  47:          public void Foo1()
  48:          {
  49:              Console.WriteLine("foo and the list");
  50:          }
  51:      }
  52:  }

Cały trik w tym przypadku, to przeniesienie odpowiedzialności na wytwarzanie obiektów z ListExtension do ExtensionsFactory. A podczas wywołania metody, należny się upewnić że fabryka już istnieje.

Myślę że wystarczy mojego wymądrzania się na dziś. Podoba się? Nie podoba się? Macie inne sposoby? Pytania? Uwagi? Cokolwiek?

ps
Mam nadzieję, że więcej wyjaśniłem niż zakręciłem.
ps2
Jak już wszystko napiszę, to czytam czy jest ok, a potem i tak mam lekkiego stresa, że coś głupiego napisałem.

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