Reactive Extensions–wprowadzenie

Kiedyś już chciałem poprowadzić cykl wpisów o Reactive Extensions i nawet napisałem pierwszy post wprowadzający do interfejsów IObservable, IObserver. Niestety po drodze przytrafiły się tematy które chciałem najpierw opisać i na końcu zrezygnowałem z tego. W między czasie kilka osób pytało o ten cykl ale nie widziałem sensu ponieważ Maciej Zbrzezny już wykonał kawał dobrej roboty i  opisał to na swoim blogu w bardzo szczegółowy sposób. Dzisiaj postanowiłem jednak napisać kilka postów o RX po swojemu, z trochę innej perspektywy – może komuś przyda to się, taką mam nadzieje. Ponadto oprócz podstaw RX chciałbym pokazać jak pisać bardziej zaawansowane rzeczy jak np. rekurencyjny scheduler, który ostatnio wykorzystałem do odpytywania bazy danych o strukturze rekurencyjnej (korzystamy z OPC Framework).

Najpierw polecam przeczytanie wpisu o IObservable oraz IObserver. Następnie proszę ściągnąć i zainstalować sam framework w wersji 1.0.

Dzisiaj postaram się odpowiedzieć na pytanie: dlaczego potrzebujemy Reactive Extensions? Jak wspomniałem RX opiera się na interfejsach IObservable, IObserver co oznacza, że RX korzysta z kolekcji push-based a nie wymagających odpytywania (pull-based collections). Najbardziej znanym źródłem danych  typu “pull-based” w .NET jest po prostu IEnumerable. Aby uzyskać takie dane należy odpytywać poszczególne elementy tzn.:

string[]elements=new []{"A", "B"};
foreach(string element in elements)
{
      Console.WriteLine(element);
}

Dla powyższego przykładu IEnumerable jest doskonały, problemy pojawią się gdy mamy do czynienia z czasochłonną operacją jak np. czytanie z web service. Na przykład taki kod byłby bardzo zły:

foreach(string element in _webService.GetElements())
{
      Console.WriteLine(element);
}

GetElements prawdopodobnie potrzebuje kilka sekund na wykonanie a to spowoduje zablokowanie wątku. W takich przykładach lepiej stosować kolekcje typu “push-based”, które opierają się na powiadomieniach (“wpychaniu” danych w strumień). W .NET są już one dobrze znane i można wymienić następujące przykłady:

  1. Zdarzenia, delegaty.
  2. ObservableCollection (oparte na zdarzeniach).
  3. Timer (również oparte na zdarzeniach).
  4. Metody asynchroniczne oparte na Begin\End (dziś wypierane przez async w .NET 4.5 ale wciąż bardzo często spotykane)
  5. Wątki (Task, Thread) i wszelkie callback’i

Jak do powyższych rozważań ma się RX? RX to ujednolicona architektura, która pozwala połączyć różne kolekcje push-based w jeden mechanizm. Łatwo zauważyć, że w .NET inaczej obsługuje się zdarzenia a inaczej callback’i a jeszcze inaczej wzorzec asynchroniczny Begin\End. RX traktuje wszystkie źródła danych (nieważne czy jest to kolekcja czy po prostu zdarzenie) jako kolekcje danych. Dzięki temu łatwe jest operowanie na zdarzeniach w których często potrzebujemy kilka ostatnich wartości. Na przykład aby narysować linie tak jak to Microsoft’owy Paint robi, należy pobrać aktualną i poprzednią pozycję kursora na ekranie. W przypadku zdarzeń jest to niemożliwe i należy ręcznie stworzyć pole w klasie, które służy jako bufor. W przypadku RX, traktujemy zdarzenia jak kolekcje i wtedy zwrócenie dwóch ostatnich elementów (dwóch ostatnich pozycji kursora) jest bardzo łatwe.

Ponadto RX oparty jest na języku zapytań LINQ. Z tego względu zwrócenie specyficznej pozycji kursora sprowadza się do jednego zapytania LINQ a nie buforowania serii wartości i tworzenia IF. Jak wspomniałem RX to ujednolicona architektura dla kolekcji push-based. Umożliwia to połączenie wiele źródeł danych (web services, zdarzenia, Begin\End) w jedno zapytanie LINQ. Przydatne to jest gdy należy np. pobrać identyfikatory z jednej bazy danych, następnie wykorzystać je jako klucze do przeczytania wartości z drugiej bazy. Dzisiaj należy obsłużyć to samemu poprzez callback’i, które zawsze są trudne w obsłudze a jeszcze trudniejsze w czytaniu. Innymi słowy, RX umożliwia synchronizacje wielu źródeł danych za pomocą jednego zapytania LINQ, które opisuje całe wykonywane zadanie. Zamiast skakać z jednej metody do drugiej tworzymy jedno (czasami długie) zapytanie, które wygląda jak zdanie opisujące daną akcję.

RX wspiera wiele platform:

  1. .NET Framework
  2. WPF\Silverlight – m.in. specjalne biblioteki ułatwiające synchronizacje z UI.
  3. Windows Phone
  4. JavaScript

To co warto zauważyć kolekcje push-based oraz pull-based służą do dokładnie tego samego – dostępu do danych. W Internecie można nawet znaleźć matematyczną transformację pomiędzy interfejsami IEnumerable\IEnumerator do IObservable\IObserver:

public interface IEnumerable<T>
{
    IEnumerator<T> GetEnumerator();
}

public interface IEnumerator<T>: IDisposable
{
    T Current { get; }
    bool MoveNext();
    void Reset();
}

// równoważne do

public interface IObservable<T>
{
    IDisposable Subscribe( IObserver<T>);
}
public interface IObserver<T>
{
    void OnNext(T);
    void OnError(Exception);
    void OnCompleted ();
}

Nie chcę na blogu pokazywać jak kro po kroku wygląda dowód matematyczny ale zobaczmy jak to wygląda po prostu od strony użytkownika. Dla pull-based odpytywanie danych wygląda oczywiście następująco:

string[]elements=new []{"A","B"};
foreach(string element in elements)
{
    Console.WriteLine(element);
}

Dla IObservable\IObserver wygląda to:

string[] elements = new[] {"A", "B"};
IObservable<string> observableElements = elements.ToObservable();
observableElements.Subscribe(Console.WriteLine);

Oba przykłady wyświetlą na ekranie taki sam wynik. IObserver\IObservable są interfejsami należącymi do .NET Framework ale z kolei metoda ToObservable to już RX Extensions. RX to zestaw rozszerzeń, które ułatwiają pracę z IObservable\IObserver. Dzięki nim możemy np. skonwertować zdarzenia .NET do IObservable. Analogicznie Metoda Observable.ToObservable służy do skonwertowania IEnumerable do IObservable. RX to nic innego jak zestaw helper’ów i metod rozszerzających.

Jak widać korzystanie IObservable sprowadza się do kilku kroków:

  1. Stworzenia źródła danych IObservable. W powyższym fragmencie wykorzystano metodę ToObservable zamieniającą IEnumerable do tego interfejsu. W RX Istnieją  jeszcze inne metody do konwersji zdarzeń czy wzorca Begin\End – o tym w następnych postach.
  2. Implementacji IObserver, który zawiera trzy metody OnNext, OnError, OnCompleted. Jest to etap opcjonalny bo można użyć wyrażenia lambda jak to zostało przedstawione w powyższym fragmencie kodu.
  3. Dokonanie subskrypcji za pomocą metody Subscribe.  Można użyć klasy stworzonej w punkcie drugim albo po prostu wyrażeń lambda.

Jeśli krok drugi nie jest jasny to proszę rozważyć następujący kod:

class CustomObserver:IObserver<string>
{
    public void OnNext(string value)
    {
        Console.WriteLine("Nastepny element:{0}",value);
    }

    public void OnError(Exception error)
    {
        Console.WriteLine("Błąd:{0}",error.Message);
    }

    public void OnCompleted()
    {
        Console.WriteLine("Koniec danych");
    }
}
internal class Program
{
    private static void Main(string[] args)
    {
        string[] elements = new[] {"A", "B"};
        IObservable<string> observableElements = elements.ToObservable();

        // z użyciem IObserver
        observableElements.Subscribe(new CustomObserver());
     
        // z użyciem lambda
        observableElements.Subscribe(
            value => Console.WriteLine("Nastepny element:{0}", value),
            error => Console.WriteLine("Błąd:{0}", error.Message),
            () => Console.WriteLine("Koniec danych"));
    }
}

Proszę zauważyć, że jest to lepsze rozwiązanie niż zwykle zdarzenia w .NET. Oprócz czystych danych otrzymywanych przez OnNext dostajemy informację o ewentualnych błędach (OnError) oraz zakończeniu strumienia danych OnCompleted. Oczywiście nie wszystkie źródła danych mają swój koniec np. MouseMove zawsze będzie wywoływało OnNext ponieważ w każdej chwili użytkownik może poruszyć myszką. W przypadku jednak Begin\End czy powyższego przykładu z elementami “A”,”B”, OnCompleted zostanie wywołany bardzo szybko.

W następnym poście przedstawię więcej metod rozszerzających RX pozwalających na tworzenie bardziej skomplikowanych źródeł danych niż zwykła konwersja IEnumerable->IObservable. Wiem, że nie jest to na razie zbyt praktyczne ale trzeba przebrnąć przez te podstawy.

Słowo kluczowe dynamic: wydajność

W zeszłym tygodniu pisałem o zastosowaniu dynamicznych zmiennych. W dzisiejszym wpisie zastanowimy się co dokładnie CLR robi z dynamic i jak to wpływa na wydajność aplikacji. Pierwszy test polega na porównaniu wydajności dodawania dwóch liczb:

private static void TestStatic()
{
   var stopwatch = Stopwatch.StartNew();

   int a = 10;
   int b = 45;
   int c = a + b;
   stopwatch.Stop();
   Console.WriteLine("Static:{0}", stopwatch.ElapsedTicks);
}
private static void TestDynamic()
{
   var stopwatch = Stopwatch.StartNew();

   dynamic a = 10;
   dynamic b = 45;
   dynamic c = a + b;
   stopwatch.Stop();
   Console.WriteLine("Dynamic:{0}",stopwatch.ElapsedTicks);
}

TestStatic bada wydajność w przypadku klasycznego wykonania kodu – wszystkie typy zmiennych znane już są na etapie kompilacji. Z kolei TestDynamic używa słowa kluczowego dynamic co oznacza, że typ pola zostanie wyznaczony dopiero w trakcie wykonywania kodu. Wykonując pierwszy raz powyższe metody otrzymano następujące wyniki:

Static: 2

Dynamic: 125810

Różnica jest oczywiście kolosalna i nieakceptowalna w większości przypadków. Dynamiczne rozwiązanie okazało się wolniejsze o ponad 60 000 razy! Aby w pełni zrozumieć otrzymany wynik należy przeanalizować jak działa DLR. Gdy pierwszy raz wykonywana jest metoda TestDynamic, DLR sprawdza czy została już ta metoda wcześniej skompilowana. Oczywiście za pierwszym razem nie została więc używając specjalnego kompilatora sprawdzany jest typ i wstawiany jest silnie typowany (w tym przypadku int). Wynik jest buforowany tak, że następne wywołania nie muszą już wykonywać tej logiki. Dla CLR “skompilowany” kod niczym nie różni się od TestStatic. Słowo dynamic przeznaczone jest dla DLR a nie CLR – w momencie wykonywania kodu dynamic jest zastępowany normalnym, statycznym typem.

Z powyższych rozważań wynika, że następne wywołania TestDynamic powinny być wydajnościowo zbliżone do TestStatic. Sprawdźmy to:

private static void Main(string[] args)
{
   TestStatic(); // szybkie
   TestDynamic();//wolne

   TestStatic(); // szybkie
   TestDynamic();// szybkie również!
}

Wynik to 2 dla TestStatic oraz 3 dla TestDynamic. Oczywiście to zbieg okoliczności, że TestDynamic ma wartość trochę większą. Należy traktować obydwa wywołania tak jakby miały identyczny lub BARDZO zbliżony poziom wydajności.  Wniosek jest taki, że dla metod, które często są wywoływane dynamic może być dobrym rozwiązaniem. W przypadku pojedynczego zastosowania zbyt wiele czasu jest poświęcane przez DLR i korzyści z zastosowania dynamic nie są wystarczające aby z niego skorzystać.

Oczywiście powyższe przykłady nie są praktycznie i dla prostych operacji typu dodanie dwóch liczb zawsze lepiej wykorzystać statyczne typowanie. W poprzednich postach pokazałem kilka praktycznych zastosowań. Istnieje jednak jeszcze bardzo ważny scenariusz użycia dla dynamic – mechanizm refleksji.  Korzystanie z niego jest dość trudne i powstały kod jest bardzo trudny w czytaniu. Sytuacja jest szczególnie zła w przypadku wykonywania właściwości typu indexer. Należy zaznaczyć, że refleksja jest wolniejsza niż klasyczne wywołanie metod. Z tego względu poniższe metody testują wydajność 3 scenariuszy:

  1. Klasyczne wywołanie metody.
  2. Wywołanie metody za pomocą refleksji.
  3. Wywołanie metody za pomocą słowa kluczowego dynamic.

Test:

internal class Program
{
    private const int N = 10000000;

    private static void TestReflectionInvoke()
    {
        MethodInfo toUpper = typeof(string).GetMethod("ToUpper",new Type[0]);

        var stopwatch = Stopwatch.StartNew();
        object test = "Hello World";
        for (int i = 0; i < N; i++)
            test = toUpper.Invoke(test, null);

        stopwatch.Stop();
        Console.WriteLine("Reflection:{0}", stopwatch.ElapsedTicks);
    }
    private static void TestStaticInvoke()
    {
        var stopwatch = Stopwatch.StartNew();
        string test = "Hello World";
        for (int i = 0; i < N; i++)
            test = test.ToUpper();

        stopwatch.Stop();
        Console.WriteLine("Static:{0}", stopwatch.ElapsedTicks);
    }
    private static void TestDynamicInvoke()
    {
        var stopwatch = Stopwatch.StartNew();        
        dynamic test = "Hello World";
        for (int i = 0; i < N;i++ )
            test = test.ToUpper();
        
        stopwatch.Stop();
        Console.WriteLine("Dynamic:{0}", stopwatch.ElapsedTicks);
    }
    private static void Main(string[] args)
    {
        TestStaticInvoke();
        TestDynamicInvoke();
        TestReflectionInvoke();
    }
}

Wyniki:

Static:15549636

Dynamic:15835424

Reflection:20069418

Dynamic wygrywa z refleksją ponieważ metoda została wykonana wiele razy. W przypadku gdy N=1 refleksja jest znacząco szybsza. Dynamic jednak znacząco wygrywa z reflection gdy DLR zbuforował już kod (następne wykonania). Pierwsze wykonanie jest zawsze wolniejsze od wywołania statycznego i refleksji. Ponadto łatwiej jest korzystać dynamic pisząc po prostu:

test = test.ToUpper();

Zamiast:

MethodInfo toUpper = typeof(string).GetMethod("ToUpper",new Type[0]);
test = toUpper.Invoke(test, null);

Ponadto warto zaobserwować w oknie output, że przy pierwszym wykonaniu dynamic ładowane są dodatkowe biblioteki:

'ConsoleApplication5.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Dynamic\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Dynamic.dll'

'ConsoleApplication5.vshost.exe' (Managed (v4.0.30319)): Loaded 'Anonymously Hosted DynamicMethods Assembly'

Code review: zła obsługa wyjątków

Już kiedyś pisałem jak obsługiwać prawidłowe wyjątki ale dzisiaj jeszcze raz chciałbym rozwinąć temat. Zacznijmy od:

private string GetData(int id)
{
    string result=null;
    
    try
    {
        result = _service.GetData(id);
    }
    catch(Exception e)
    {
    }
    return result;
}

Jest to oczywiście skrajnie złe rozwiązanie ponieważ wszystko ignorujemy. Na szczęście programiści rzadko popełniają powyższy błąd. Niestety dużo częściej popełnianym błędem jest:

private string GetData(int id)
{
   string result;
   try
   {
       result = _service.GetData(id);
   }
   catch(Exception e)
   {
       Logger.Log(e.Message);
       // jakas logika
       result = null;
   }
   return result;
}

Pierwszy problemem jest wyłapywanie zbyt ogólnego wyjątku Exception. W zdecydowanej większości przypadków jest to zły nawyk. Klauzuli catch używamy wyłącznie gdy:

  1. Mamy wystarczającą wiedzę co się stało.
  2. Spodziewamy się specyficznego wyjątku.
  3. Potrafimy go naprawić np. poprzez izolację procesu albo w przypadku web service można ponownie spróbować połączyć się z usługą.
  4. Możliwe typy wyjątków zostały rozpoznane już na etapie projektowania.

Co oznacza łapanie wyjątku Exception? Czy możliwe jest, że reakcja na wyjątek podczas wywołania GetData jest zawsze taka sama? Oczywiście jest to niemożliwe – nie jesteśmy w stanie przewidzieć wszystkich wyjątków stąd Exception jest prawie zawsze zły. Co jeśli wywołanie GetData spowodowało OutOfMemoryException? Czy aby na pewno chcemy mieć taką samą obsługę błędów? Z tego względu zawsze łapmy wyjątki, których spodziewaliśmy się na etapie projektowania:

private string GetData(int id)
{
   string result;
   try
   {
       result = _service.GetData(id);
   }
   catch(TimeOutException e)
   {
       // jakas logika odpowiedzialna za recovery mechanism.
   }
   return result;
}

Co z logami? Jeśli chcemy wykonywać je, wtedy lepiej to zrobić w globalnym miejscu a nie w catch’u dla każdej metody.

Analogicznym błędem popełnianym przez bardzo wielu programistów jest wyrzucanie wyjątków typu Exception. Moim zdaniem Exception powinien być typu abstrakcyjnego, ewentualnie zawierać konstruktor niepubliczny. Umożliwienie wyrzucanie wyjątków Exception powoduje, że czasami programiści po prostu muszą łapać ogólne obiekty. ZAWSZE należy wyrzucać wyjątek specyficzny tak, że użytkownicy danej klasy nie musza łapać Exception. Uważam, że nie ma wyjątków od tej reguły.

Dobrym zwyczajem jest tworzenie wyjątków z modyfikatorem sealed i bardzo ostrożne tworzenie klas bazowych dla wyjątków. Wyobraźmy sobie, że programista tworzy następującą klasę:

class TimeoutException:Exception
{
    // kod tutaj  
}

Następny użytkownik napisał kod:

private string GetData(int id)
{
   string result;
   try
   {
       //jaks kod
   }
   catch (TimeoutException e)
   {
       // jakas logika odpowiedzialna za recovery mechanism.
   }
   return result;
}

Co się stanie jeśli autorzy biblioteki stworzą następny wyjątek dziedziczony po TimeoutException?

class AnotherTimeoutException : TimeoutException
{
}

Oczywiście metoda GetData wyłapie wszystkie wyjątki dziedziczące po TimeoutException. Czasami jest to poprawne zachowanie ale należy być świadomym ryzyka tworzenia bazowych klas dla wyjątków. Dobrym zwyczajem jest zatem używanie modyfikatora sealed. Oczywiście są przypadki gdzie powyższe przykłady nie są złe – wszystko zależy od konkretnego scenariusza. Powyższe przykłady miały na celu pokazanie istniejącego ryzyka.

Wspomniałem, że czasami catch(Exception e) jest akceptowalny. Oto przypadki, które kwalifikują się do takiego scenariusza:

  1. Błędna architektura. Miałem już kiedyś przypadek, że ze względu na błąd w zewnętrznej bibliotece to był jedyny sposób na prawidłowe zachowanie aplikacji. Dotyczy to PrintPreview w Windows ale niestety już nie pamiętam dokładnie kodu.
  2. Błędna architektura – jeśli kod jest złe napisany i zawsze wyrzuca typ Exception zamiast specyficznych wyjątków.
  3. W catch jest re-throw, który i tak spowoduje, wyrzucenie wyjątku. Czasami taki mechanizm stosuje się aby dodać specyficzne informacje, zależne od kontekstu. Jeśli na końcu catch jest re-throw wtedy kod jest akceptowalny.

Zamykanie aplikacji w przypadku awarii: Environment.FailFast

Wszyscy dążymy do oprogramowania, które zawsze działa ale oczywiście musimy przygotować się na przypadki w których wystąpił wyjątek i nie wiadomo jak go obsłużyć. Najgorszą reakcją jest oczywiście pozwolenie aplikacji dalej działać co może spowodować nieoczekiwane efekty oraz popsuć po prostu dane. Musimy wszystko zrobić aby nie dopuścić do niespójności danych. W przypadku gdy wiemy, że aplikacja nie może kontynuować swojego działania musimy bezwzględnie zakończyć cały proces albo AppDomain. Należy po prostu odizolować “zepsuty” fragment procesu. Z tego względu korzystanie z AppDomain jest wysoce pożądane (szczególnie dla aplikacji serwerowych, które muszą bez przerwy działać). W tym poście chciałbym przedstawić  metodę Enviroment.FailFast, która wykonuje następujące czynności:

  1. Zamyka natychmiast aplikację, przerywając nawet wykonywanie klauzuli finally.
  2. Wykonuje zrzut pamięci.
  3. Wykonuje log, który można później zobaczyć w Event Viewer.

Zdecydowanie powinniśmy preferować Enviroment.FailFast zamiast Enviroment.Exit, który po prostu zamyka aplikację i nie dostarcza żadnych wartościowych danych, które mogą być potem przydatne dla programistów. Przykład:

private static void Main(string[] args)
{
   try
   {
       //brzydkie ale tylko dla testow
       throw new Exception();
   }
   catch // oczywiscie brzydkie, w praktyce podaje sie specyficzne wyjatki
   {
       Environment.FailFast("Jakis komunikat");
   }
}

Jako parametr podajemy jakiś tekst, opisujący przyczynę awarii. Pod odpaleniu aplikacji zobaczymy Microsoft’owy komunikat błędu:

image

 

W taki sposób możemy oczywiście debugować aplikację. W EventViewer z kolei również powstał odpowiedni wpis:

image

Jak CLR\C# ułatwia pracę z NULLABLE?

Struktura Nullable jest już dobrze znana w świecie .NET. Pozwala na zasymulowanie wartości NULL dla typów prostych (value types). C# posiada jednak wiele ułatwień, które chciałbym opisać w dzisiejszym poście. Prawdopodobnie wiele czytelników korzystało z nich ale nie wiedziała, że to ułatwienie ze strony kompilatora a nie samej struktury Nullable. Zacznijmy od kodu źródłowego Nullable:

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Nullable<T> where T : struct
{
    // These 2 fields represent the state
    private Boolean hasValue;
    internal T value;

    public Nullable(T value)
    {
        this.value = value;
        this.hasValue = true;
    }

    public Boolean HasValue
    {
        get { return hasValue; }
    }

    public T Value
    {
        get
        {
            if (!hasValue)
            {
                throw new InvalidOperationException(
                    "Nullable object must have a value.");
            }
            return value;
        }
    }

    public T GetValueOrDefault()
    {
        return value;
    }

    public T GetValueOrDefault(T defaultValue)
    {
        if (!HasValue) return defaultValue;
        return value;
    }

    public override Boolean Equals(Object other)
    {
        if (!HasValue) return (other == null);
        if (other == null) return false;
        return value.Equals(other);
    }

    public override int GetHashCode()
    {
        if (!HasValue) return 0;
        return value.GetHashCode();
    }

    public override string ToString()
    {
        if (!HasValue) return "";
        return value.ToString();
    }

    public static implicit operator Nullable<T>(T value)
    {
        return new Nullable<T>(value);
    }

    public static explicit operator T(Nullable<T> value)
    {
        return value.Value;
    }
}

Jak widać, struktura sama w sobie nie jest zbyt skomplikowana. Ważnym elementem są operatory konwersji (dwie ostatnie metody), pozwalające na konwersje pomiędzy typem podstawowym  a Nullable. Gdyby jednak nie wsparcie C# korzystanie nie byłoby takie łatwe:

1.Zamiast pisać Nullable<int> wystarczy int? – znak zapytania oznacza, że tym ma być owinięty w Nullable:

int? a;
Nullable<int> b; // to samo co wyzej

2. Pomimo, że Nullable nie posiada żadnych przeładowanych operatorów, wciąż istnieje możliwość z ich korzystania:

int? a = 5;
int? b = 10;

int? r = a + b;

C# w zależności od operatora w różny sposób interpretuje wartość NULL. Na przykład jeśli jakaś wartość jest równa NULL wtedy operatory <,>,<=,>= zwracają zawsze false. W przypadku ==,!= zachowują się w identyczny sposób jak dla typów referencyjnych. Podobnych zasad jest sporo i dlatego operowanie na takich typach jest dość skomplikowane – lepiej tego unikać bo łatwo popełnić błąd.

Ponadto operatory generują dużo kodu. Powyższa operacja a+b wygeneruje następujący kod:

Nullable<Int32> nullable1 = a;
Nullable<Int32> nullable2 = b;
if (!(nullable1.HasValue & nullable2.HasValue)) {
return new Nullable<Int32>();
}
return new Nullable<Int32>(nullable1.GetValueOrDefault() + nullable2.GetValueOrDefault());

Proszę sobie wyobrazić jak będzie to wyglądało dla skomplikowanych wzorów matematycznych.

Jeśli operujemy na własnym typie np. Matrix i mamy w nim przeładowane różne operatory, C# zachowa się prawidłowo i będzie możliwe wykonanie operacji na macierzach:

Matrix? m1,m2;

var r= m1+m2; // wywoła operatory Matrix a nie Nullable<Matrix>, które nie istnieją.

3. Kolejnym ułatwieniem, tym razem ze strony CLR jest boxing. Jak zachowa się poniższy kod?

int? a = null;
object boxedA = a;

Mogłoby się wydawać, że zostanie utworzona struktura Nullable<int> i następnie dokonany boxing. Jednak CLR jest na tyle inteligentny, że jeśli Nullable<int> nie ma ustawionej wartości wtedy boxing polega na przypisaniu referencyjnego NULL a nie całego Nullable<int>.

4. Możliwe są następujące sposoby unboxingu:

int val = 5;
object boxedInt = val;

int? unboxed1 = (int?)boxedInt;
int unboxed2 = (int)boxedInt;

Unboxing do Nullable<int> jest możliwy mimo, że tak naprawdę inny typ został zaboksowany (System.Int32). W sposób logiczny również zachowa się unboxing referencyjnego NULL:

object boxedInt = (object)null;

int? unboxed1 = (int?)boxedInt; // OK

5. Ciekawostka: co wyświetli poniższy kod?

int? unboxed1 = 5;
Console.WriteLine(unboxed1.GetType());

Nullable chce się zachować jak najbardziej transparentnie i wynikiem będzie System.Int32 a nie System.Int32?.

5. Jak widać z kodu źródłowego, Nullable nie implementuje żadnych interfejsów ale mimo tego możliwe jest:

int? val = 5;
int result=((IComparable) val).CompareTo(5);

Powyższe obserwacje pokazują, że Nullable zachowuje się prawie jak typ referencyjny. Oczywiście należy pamiętać, że nim nie jest i przypisanie jednego Nullable do drugiego spowoduje skopiowanie wartości. Aczkolwiek, wszelkie konwersje  są naturalne i oczekiwane. Należy jednak sobie zdawać sprawę, że jest to wsparcie ze strony C#\CLR i samemu tego kodu nie moglibyśmy po prostu zaimplementować.

Zastosowanie dynamic: DynamicObject

Dziś kolejny post z cyklu zastosowanie słowa kluczowego dynamic. Ostatnio pisałem o ExpandoObjet, który jest dynamicznym kontenerem na metody i dane. DynamicObject pozwala z kolei tworzyć wrappery na różne klasy. Zacznijmy od przykładu:

internal class Program
{
    public class CustomWrapper : DynamicObject
    {
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = "Hello World";
            return true;
        }
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            return true;
        }
    }
    private static void Main(string[] args)
    {
        dynamic wrapper=new CustomWrapper();
        Console.WriteLine(wrapper.AnyProperty);// Hello world
        wrapper.AnyProperty = "test";
        Console.WriteLine(wrapper.AnyProperty);// Hello World
    }
}

Zawsze dziedziczymy po DynamicObject – nie używamy tej klasy bezpośrednio.

Gdy próbujemy uzyskać dostęp do jakiegoś elementu w klasie DynamicObject, najpierw sprawdzane są normalne właściwości, zdefiniowane w klasie. Jeśli dana właściwość nie istnieje wtedy wywoływana jest metoda TryGetMember, która za pomocą result zwraca wynik. W powyższym przykładzie każde wywołanie nowej właściwości zwraca tekst Hello World. TrySetMember pełni funkcję analogiczną ale wywoływany jest w momencie ustawiania wartości. Napiszmy również wrapper dla klasy string, który potrafi wywoływać różne metody:

 public class CustomWrapper : DynamicObject
 {
        private string _container = "Hello World";

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            result= typeof(string).InvokeMember(
                 binder.Name,
                 BindingFlags.InvokeMethod |
                 BindingFlags.Public |
                 BindingFlags.Instance,
                 null, _container, args);

            return true;
        }
    }
    private static void Main(string[] args)
    {
        dynamic wrapper=new CustomWrapper();
        Console.WriteLine(wrapper.ToUpper());// HELLO WORLD        
    }
}

Pamiętacie z poprzedniego postu ExpandoObject o XML to LINQ? Alexandra Rusina pokazuje na swoim blogu również jak zrobić to za pomocą DynamicObject:

public class DynamicXMLNode : DynamicObject
{
    XElement node;
    public DynamicXMLNode(XElement node)
    {
        this.node = node;
    }
    public DynamicXMLNode()
    {
    }
    public DynamicXMLNode(String name)
    {
        node = new XElement(name);
    }
    public override bool TrySetMember(
        SetMemberBinder binder, object value)
    {
        XElement setNode = node.Element(binder.Name);
        if (setNode != null)
            setNode.SetValue(value);
        else
        {
            if (value.GetType() == typeof(DynamicXMLNode))
                node.Add(new XElement(binder.Name));
            else
                node.Add(new XElement(binder.Name, value));
        }
        return true;
    }
    public override bool TryGetMember(
        GetMemberBinder binder, out object result)
    {
        XElement getNode = node.Element(binder.Name);
        if (getNode != null)
        {
            result = new DynamicXMLNode(getNode);
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }
}

Na prawdę świetny trick! Stworzyliśmy prawdziwy wrapper dla XElement. Teraz zamiast pisać:

XElement contactXML =
    new XElement("Contact",
    new XElement("Name", "Patrick Hines"),
    new XElement("Phone", "206-555-0144"),
    new XElement("Address",
        new XElement("Street1", "123 Main St"),
        new XElement("City", "Mercer Island"),
        new XElement("State", "WA"),
        new XElement("Postal", "68042")
    )
);

Wystarczy:

dynamic contact = new DynamicXMLNode("Contacts");
contact.Name = "Patrick Hines";
contact.Phone = "206-555-0144";
contact.Address = new DynamicXMLNode();
contact.Address.Street = "123 Main St";
contact.Address.City = "Mercer Island";
contact.Address.State = "WA";
contact.Address.Postal = "68402";

DynamicXmlNode to wrapper dla XElement. Kiedykolwiek wywołujemy właściwości typu Address czy Street, wewnątrz wrapper’a ustawiane są odpowiednie pola XmlElement. a zwracany jest dynamiczny DynamicXMLNode.  Autorka blogu, pokazuje również jak skonwertować DynamicXMLNodde do string:

public override bool TryConvert(
    ConvertBinder binder, out object result)
{
    if (binder.Type == typeof(String))
    {
        result = node.Value;
        return true;
    }
    else
    {
        result = null;
        return false;
    }
}

Teraz możliwe jest:

dynamic contact = new DynamicXMLNode("Contacts");
contact.Name = "Patrick Hines";
contact.Phone = "206-555-0144";
contact.Address = new DynamicXMLNode();
contact.Address.Street = "123 Main St";
contact.Address.City = "Mercer Island";
contact.Address.State = "WA";
contact.Address.Postal = "68402";

string text = contact.Phone; // wywoła TryConvert

Osobiście uważam, że warto dodać jeszcze jedną konwersję:

public override bool TryConvert(
ConvertBinder binder, out object result)
{
  if (binder.Type == typeof(XElement))
  {
      result = node;
      return true;
  }
  else
  {
      result = null;
      return false;
  }
}

private static void Main(string[] args)
{
   dynamic contact = new DynamicXMLNode("Contacts");
   contact.Name = "Patrick Hines";
   contact.Phone = "206-555-0144";
   contact.Address = new DynamicXMLNode();
   contact.Address.Street = "123 Main St";
   contact.Address.City = "Mercer Island";
   contact.Address.State = "WA";
   contact.Address.Postal = "68402";

   XElement element = contact.Phone;
}

W skrócie aby zaimplementować wrapper za pomocą DynamicObject należy:

  1. Stworzyć klasę dziedziczącą po DynamicObject.
  2. Dodać prywatne pole, które reprezentuje wrappowany element (w powyższych przykładach są to string lub XElement).
  3. Przeładować metody, które obsługują operacje zwracania, ustawiania, wywołania metod, konwersji itp.

Zastosowanie dynamic: ExpandoObject

Kiedyś już wspomniałem o słowie kluczowych dynamic. W tym i następnych wpisach chciałbym przedstawić praktyczne zastosowanie tego mechanizmu. Na koniec wyjaśnię, jak dynamic jest zaimplementowany przez CLR i jak bardzo spowalnia aplikację…

Programiści używający ASP.NET MVC z pewnością rozpoznają zasadę działania ExpandoObject. Klasa umożliwia tworzenie dynamicznych kontenerów. Na przykład:

private static void Main(string[] args)
{
   dynamic bag = new ExpandoObject();
   bag.FirstName = "Piotr";
   bag.LastName = "Zielinski";

   DisplayBag(bag);
}
private static void DisplayBag(dynamic bag)
{
   Console.WriteLine(bag.FirstName);
   Console.WriteLine(bag.LastName);
}

ExpandoObject umożliwia doczepianie nowych elementów do klasy. Gdy użytkownik próbuje zapisać coś w właściwości, nowe pole jest dodawane dynamicznie. Możliwe jest również tworzenie metod:

dynamic bag = new ExpandoObject();
bag.FirstName = "Piotr";
bag.LastName = "Zielinski";
bag.Print = (Action) (() =>
                        {
                            Console.WriteLine(bag.FirstName);
                            Console.WriteLine(bag.LastName);
                        });

bag.Print();

ExpandoObject implementuje IDictionary<string,object>, który przechowuje dodane właściwości i metody. Takim sposobem można usuwać wcześniej dodane elementy tzn.:

dynamic bag = new ExpandoObject();
bag.FirstName = "Piotr";
bag.LastName = "Zielinski";
bag.Print = (Action) (() =>
                        {
                            Console.WriteLine(bag.FirstName);
                            Console.WriteLine(bag.LastName);
                        });

((IDictionary<string, object>) bag).Remove("FirstName");
bag.Print();

Kod zakończy się wyjątkiem podczas wykonywania Console.WriteLine(bag.FirstName) ponieważ ta właściwość została usunięta w przedostatniej linii kodu.

Nie jestem pewny co ASP.NET MVC używa wewnętrznie ale taki scenariusz na ExpandoObject jest jak najbardziej trafny. Inny scenariusz znalazłem na jednym z blogów MSDN (Alexandra Rusina) a mianowicie uproszczenie składni LINQ Tlo XML. Wyobraźmy sobie, że mamy:

XElement contacts =
    new XElement("Contacts",
        new XElement("Contact",
            new XElement("Name", "Patrick Hines"), 
            new XElement("Phone", "206-555-0144"),
            new XElement("Address",
                new XElement("Street1", "123 Main St"),
                new XElement("City", "Mercer Island"),
                new XElement("State", "WA"),
                new XElement("Postal", "68042")
            )
        )
    );

Kod bardzo nieczytelny a jeszcze trudniejszy w pisaniu zapytań. Za pomocą ExpandoObject można to uprościć do:

dynamic contact = new ExpandoObject();
contact.Name = "Patrick Hines";
contact.Phone = "206-555-0144";
contact.Address = new ExpandoObject();
contact.Address.Street = "123 Main St";
contact.Address.City = "Mercer Island";
contact.Address.State = "WA";
contact.Address.Postal = "68402";

Wyświetlenie ulicy teraz jest dużo prostsze i sprowadza się do:

Console.WriteLine(contact.Address.Street);

W LINQ to XML musielibyśmy:

Console.WriteLine((string)contactXML.Element("Address").Element("Street"));

Podobnie można tworzyć kolekcje List<ExpandoObject> zamiast korzystać XElement.Descendants. Szczególnie zapytania LINQ wyglądają krócej. W LINQ To XML musielibyśmy pisać:

var phonesXML = from c in contactsXML.Elements("Contact")
                where c.Element("Name").Value == "Patrick Hines"
                select c.Element("Phone").Value;

Za pomocą dynamic wystarczy:

var phones = from c in (contacts as List<dynamic>)
             where c.Name == "Patrick Hines"
             select c.Phone;

Autorka wspomnianego blogu również pokazuje jak można skonwertować ExpandoObject (który oczywiście nie ma nic wspólnego z LINQ to XML) do XElement:

private static XElement expandoToXML(dynamic node, String nodeName)
{
    XElement xmlNode = new XElement(nodeName);

    foreach (var property in (IDictionary<String, Object>)node)
    {

        if (property.Value.GetType() == typeof(ExpandoObject))
            xmlNode.Add(expandoToXML(property.Value, property.Key));

        else
            if (property.Value.GetType() == typeof(List<dynamic>))
                foreach (var element in (List<dynamic>)property.Value)
                    xmlNode.Add(expandoToXML(element, property.Key));
            else
                xmlNode.Add(new XElement(property.Key, property.Value));
    }
    return xmlNode;
}

Myślę, że LINQ To XML jest dobrym przykładem na nieprzejrzyste API. Niestety autorzy prawdopodobnie nie mieli wyboru ponieważ LINQ TO XML powstał dużo wcześniej niż dynamic. Warto jednak wciąć pod uwagę programowanie dynamiczne projektując różnego rodzaju Frameworki.

Kiedy stałe (const) są złą praktyką?

Kilka postów wcześniej pisałem o różnicach między const a read-only. Dzisiaj  chciałbym pokazać scenariusz, który pokazuje kiedy NIE używać słowa const. Const jest doskonałym rozwiązaniem dla liczb, które są po prostu ZAWSZE stałe. Przykład? Liczba PI ma jedną wartość i oczywiście nigdy się nie zmieni.

Istnieją jednak przypadki gdzie liczby w kodzie są zastępowane stałymi co nie zawsze jest dobrym wzorcem. Załóżmy, że mamy klasę przechowującą pewne parametry algorytmu:

namespace ClassLibrary1
{
    public class Consts
    {
        public const int N = 10;
    }
}

Proszę zauważyć, że klasa jest zlokalizowana w osobnej bibliotece. Następnie w innej aplikacji musimy wykorzystać taką stałą:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Consts.N);
        }
    }
}

Co prawda uniknęliśmy magic numbers ale niestety kod nie jest elastyczny. Jak wiemy, const nie alokuje pamięci a jest wstawiany w procesie kompilacji. Jeśli użytkownik biblioteki zmieni wartość N wtedy uruchomienie aplikacji nie wyświetli nowej wartości. Po prostu liczba jest wstawiana w czasie kompilacji i zawsze zarówno ClassLibrary1 jak i ConsoleApplication1 muszą zostać przekompilowane. Tak naprawdę N nie jest stałą  (bo może ulec zmianie) a wartością   tylko do odczytu i lepiej wykorzystać readonly:

public static class Consts
{
   public static readonly int N = 55;
}

Przy każdej modyfikacji N wystarczy tylko przekompilować ClassLibrary1. Wniosek taki, że const lepiej używać dla symboli (które zawsze mają taką samą nazwę) a readonly dla wartości, które może modyfikować tylko autor.  Oczywiście ma to znaczenie w aplikacjach modularnych a nie składających się z pojedynczej biblioteki. Często stałe są używane wyłącznie w jednej bibliotece i wtedy również nie ma problemu z wersjonowaniem ich.

Formatowanie tekstu oraz atrybuty IFormatProvider, IFormattable, and ICustomFormatter

Dziś trochę o formatowaniu tekstu. Można je wykonać na wiele sposób. Osoby nie znające powyższych interfejsów zwykle tworzą własne metody zwracające wynik w odpowiednim formacie. Załóżmy, że mamy następującą klasę:

class PhoneNumber
{
   private readonly string _extension;
   private readonly string _phoneNumber;

   public PhoneNumber(string extension,string phoneNumber)
   {
       _extension = extension;
       _phoneNumber = phoneNumber;
   }
}

Na przykładzie powyższej klasy będę starał się po kolei przedstawić zastosowanie wspomnianych interfejsów. Proszę nie zwracać uwagi na strukturę wewnętrzną klasy – oczywiście można to lepiej zaimplementować niż przekazanie numeru w konstruktorze za pomocą string – nie  w tym jednak rzecz. Załóżmy, że użytkownik chce wyświetlić na ekranie kierunkowy i numer telefonu w formacie (extension)number. Brzydkim rozwiązaniem byłoby:

class PhoneNumber
{
   private readonly string _extension;
   private readonly string _phoneNumber;

   public PhoneNumber(string extension,string phoneNumber)
   {
       _extension = extension;
       _phoneNumber = phoneNumber;
   }
   public string GetFormattedNumberWithExt()
   {
       return string.Format("({0}){1}", _extension, _phoneNumber);
   }
}

private static void Main(string[] args)
{
   var phoneNumber=new PhoneNumber("95","2142");
   Console.WriteLine(phoneNumber.GetFormattedNumberWithExt());
}

Oczywiście jest to mieszanie logiki ze sposobem wyświetlenia oraz nie spełnia to zasady Open\Closed principle. Za każdym razem gdy chcemy zmodyfikować sposób formatowania zmuszeni jesteśmy do modyfikowania klasy. Zacznijmy od implementacji interfejsu IFormattable:

class PhoneNumber:IFormattable
{
   private readonly string _extension;
   private readonly string _phoneNumber;

   public PhoneNumber(string extension,string phoneNumber)
   {
       _extension = extension;
       _phoneNumber = phoneNumber;
   }
   private string GetFormattedNumberWithExt()
   {
       return string.Format("({0}){1}", _extension, _phoneNumber);
   }

   public string ToString(string format, IFormatProvider formatProvider)
   {
       if (string.Equals(format, "full", StringComparison.OrdinalIgnoreCase))
           return GetFormattedNumberWithExt();

       return _phoneNumber;
   }
}

private static void Main(string[] args)
{
   var phoneNumber=new PhoneNumber("95","2142");
   Console.WriteLine("{0:full}", phoneNumber);
}

Rozwiązanie trochę lepsze – wykorzystujemy standard .NET do formatowania tekstu. Niestety wciąż łamie to zasadę Open\Closed principle. Co jeśli chcemy np. wyświetlić sam kierunkowy lub kierunkowy oraz numer w postaci gwiazdek? Do tego służą interfejsy ICustomFormatter oraz IFormatProvider.

IFormatProvider jest klasą przekazywaną do IFormattable.ToString, która potrafi zwrócić klasę, która odpowiedzialna jest z kolei za formatowanie. Można oba interfejsy zaimplementować w tej samej klasie np.:

internal class Program
{
    class PhoneFormatter:ICustomFormatter,IFormatProvider
    {
        private string GetFormattedNumberWithExt(PhoneNumber phoneNumber)
        {
            return string.Format("({0}){1}", phoneNumber.Extension, phoneNumber.Number);
        }
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            PhoneNumber phoneNumber = arg as PhoneNumber;
            if(phoneNumber==null)
                throw new ArgumentException();

            if (string.Equals(format, "full", StringComparison.OrdinalIgnoreCase))
                return GetFormattedNumberWithExt(phoneNumber);

            return phoneNumber.Number;
        }

        public object GetFormat(Type formatType)
        {
            return this;
        }
    }
    public class PhoneNumber
    {    
        public PhoneNumber(string extension,string phoneNumber)
        {
            Extension = extension;
            Number = phoneNumber;
        }
        public string Extension { get; private set; }
        public string Number { get; private set; }
    }
  
    private static void Main(string[] args)
    {
        var phoneNumber = new PhoneNumber("95", "2142");
        Console.WriteLine(string.Format(new PhoneFormatter(), "{0:full}",phoneNumber));
    }
}

W takim sposób odizolowaliśmy, logikę od prezentacji. Co jest gdy chcemy aby PhoneNumber zawsze miał jakiś domyślny formatter tak, że użytkownik nie musi za każdym razem tworzyć instancji PhoneNumberFormatter. Innymi słowy chcemy aby użytkownik mógł wykonać następujący kod:

var phoneNumber = new PhoneNumber("95", "2142");
Console.WriteLine(string.Format("{0:full}",phoneNumber));

Wystarczy, że PhoneNumber.ToString sprawdzi czy Provider jest równy NULL:

public class PhoneNumber:IFormattable
{    
   public PhoneNumber(string extension,string phoneNumber)
   {
       Extension = extension;
       Number = phoneNumber;
   }
   public string Extension { get; private set; }
   public string Number { get; private set; }

   public object GetFormat(Type formatType)
   {
       return new PhoneFormatter();
   }

   public string ToString(string format, IFormatProvider formatProvider)
   {
       IFormatProvider provider;

       if (formatProvider == null)
           provider = new PhoneFormatter();
       else
           provider = formatProvider;

       var formatter = provider.GetFormat(typeof(PhoneNumber)) as ICustomFormatter;
       
       if (formatter != null)
           return formatter.Format(format, this, formatProvider);
       
       return ToString();
   }
}

Na zakończenie chciałbym zaznaczyć, że CultureInfo implementuje IFormatProvider, który zwraca np. NumberFormatInfo czy DatetimeFormatInfo .

Link: StringBuilder i ciekawostka o implementacji wewnętrznej w .NET 4.0

Implementacja wewnętrzna StringBuilder uległa zmianie w .NET 4.0 (teraz używana jest lista jednokierunkowa zamiast tablicy) i z tego względu ustawienie właściwości Length na zero może spowodować znaczącą utratę wydajności. Polecam przeczytanie tego wpisu:

http://blogs.msdn.com/b/jankrivanek/archive/2012/11/30/stringbuilder-performance-issues-in-net-4-0-and-4-5.aspx