Podstawy .NET: dwuetapowa kompilacja i WinDbg do analizy JIT

Posted August 28th, 2014 by Piotr Zieliński
Categories: C#, Patterns & Practices

Tak naprawdę, moje posty powinienem rozpocząć od wyjaśnienia czym jest JIT. To jedno z podstawowych pojęć, które pojawia się w przypadku omawiania .NET. Wolałem jednak najpierw pokazać kilka programów napisanych w IL Assembly.  Kilka postów powinno dać już jakiś obraz czym jest IL.  Oczywiście kod piszemy w C# lub w innym języku wysokiego poziomu, więc wystarczy abyśmy ogólnie mieli pojęcie o IL.

Co to jest więc kompilacja JIT? Czym różni się od klasycznej? W językach niezarządzanych takich jak CPP, kompilator wygeneruje kod maszynowy. W świecie .NET kompilacja jest dwuetapowa. Używając Visual Studio nie generujemy kodu maszynowego, a właśnie IL – język pośredni, który na początku jest kompletnie bezużyteczny. Pomimo, że przypomina on kod maszynowy, to nie można go uruchomić na żadnej maszynie bez wcześniejszego przetworzenia.

Wspomniane “przetworzenie” to kolejna kompilacja, a mianowicie JIT (Just in Time).  W większości przypadków odbywa się w czasie rzeczywistym, w momencie pierwszego wywołania danej metody. Nie JIT’ujemy całej aplikacji od razu bo byłoby to zbyt wolne. Jeśli  w ogóle nie wywołamy jakieś metody, po prostu nie zostanie wygenerowany kod maszynowy dla niej. JIT zatem, to drugi etap kompilacji, który wygeneruje już konkretny kod maszynowy, bardzo często specyficzny dla konkretnej architektury. Ma to wiele zalet, takich jak niezależność od CPU i możliwość optymalizacji kodu dla konkretnej konfiguracji.

Naturalne jest, że JIT dodaje pewien overhead do aplikacji. Wywołanie każdej metody pierwszy raz jest wolniejsze niż kolejne. Z tego względu, jeśli piszemy jakieś benchmarki, musimy mieć to na uwadze, że wyniki mogą być sfałszowane przez JIT. Istnieje możliwość z JIT’owania całej aplikacji przed uruchomieniem programu, ale o tym kiedy indziej. Polecam również mój stary wpis o tzw. MultiCore JIT, który został dodany w .NET 4.5 i ma na celu przyśpieszenie JIT poprzez zrównoleglenie kompilacji.

Podkreślę jeszcze raz, że drugi etap komplikacji (JIT) w przeciwieństwie do pierwszego (Visual Studio, kompilator C#), nie jest wykonywany dla całej aplikacji, a wyłącznie dla konkretnej metody. Po prostu dzieje się to w czasie rzeczywistym i nie ma tyle czasu, aby analizować cały kod. JIT zatem nie dysponuje informacjami o całym kodzie i może wykonać optymalizacje dotyczące wyłącznie kawałka kodu ( w przeciwieństwie do komplikacji c#). Prawdziwa optymalizacja wynika jednak z faktu, że JIT bierze pod uwagę architekturę komputera (CPU) i może wygenerować instrukcje maszynowe , które teoretycznie powinny być optymalne.

Większość książek zawiera powyższe teoretyczne informacje i z tego względu, lepiej popraktykować tutaj. Posłużę się tutaj narzędziem windbg.exe. Jeśli nie pracowaliście z tym debuggerem to polecam MSDN. Windbg.exe jest bardzo przydatny w środowisku produkcyjnym, gdzie nie mamy kodu źródłowego i tym bardziej Visual Studio. Narzędzie jest dość trudne w obsłudze, ale przydatne w eksperymentowaniu oraz analizowaniu błędów, które zdarzyły się na produkcji (np. poprzez memory dump).

image

Nasz eksperyment przeprowadzimy na:

class Program
{
   static void Main(string[] args)
   {
       Display("1");
       Console.ReadLine();
       Display("2");
   }

   private static void Display(string text)
   {
       Console.WriteLine(text);
   }
}

Wywołujemy tam dwukrotnie Display. Za pierwszym razem powinna zostać dokonana kompilacja JIT, a przy drugim wywołaniu, będziemy już dysponować kodem maszynowym.

Pierwszy etap kompilacji, który wygeneruje IL jest prosty i wykonujemy go np. bezpośrednio z Visual Studio. Poskutkuje to wygenerowanym plikiem EXE:

image

Windbg, który stanowi część Windows SDK, jest ogólnym narzędziem do debuggowania. Domyślnie nie wie nic o CLR i zarządzanym kodzie. Z tego względu, musimy zainstalować dodatkowe rozszerzenie SOS.DLL. Aby załadować SOS wystarczy wykonać polecenie:

.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll

Kolejna komenda to:

sxe ld:clrjit

Potrzebujemy jej, aby debugger został powiadomiony, gdy CLRJIT jest załadowany.

Następnie wpisujemy g, aby kontynuować:

g

Dzięki poprzedniemu poleceniu zobaczymy:

0:000> g
(1134.17d8): Unknown exception - code 04242420 (first chance)
ModLoad: 730f0000 7316d000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll
eax=00000000 ebx=00800000 ecx=00000000 edx=00000000 esi=00000000 edi=7f06e000
eip=7721cfbc esp=00bce5dc ebp=00bce638 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!NtMapViewOfSection+0xc:
7721cfbc c22800          ret     28h

Ustawmy breakpoint na metodzie Display za pomocą:

!bpmd ConsoleApplication12.exe ConsoleApplication12.Program.Main

Mamy dokładnie jedną metodę o takiej nazwie więc debugger wyświetli:

Found 1 methods in module 00cc2ed4...
MethodDesc = 00cc37a8
Adding pending breakpoints...

00cc37a8 to identyfikator metody. Dzięki niemu możemy wyświetlić IL wpisując polecenie:

!dumpil 00cc37a8

Wynik:

IL_0000: nop 
IL_0001: ldstr "1"
IL_0006: call ConsoleApplication12.Program::Display
IL_000b: nop 
IL_000c: call System.Console::ReadLine 
IL_0011: pop 
IL_0012: ldstr "2"
IL_0017: call ConsoleApplication12.Program::Display
IL_001c: nop 
IL_001d: ret 

Taki sam kod IL zobaczymy oczywiście w Reflector. Nie powinno być dla nas zaskoczeniem, że pierwszy etap kompilacji został wykonany – mamy w końcu EXE. Naszym celem jest udowodnienie, że JIT nie został jeszcze wykonany, ponieważ metoda nie została wywołana.

Kolejna komenda, wyświetli kod maszynowy:

!u 00cc37a8

Wynik:

00a30050 55              push    ebp
00a30051 8bec            mov     ebp,esp
00a30053 83ec08          sub     esp,8
00a30056 33c0            xor     eax,eax
00a30058 8945f8          mov     dword ptr [ebp-8],eax
00a3005b 894dfc          mov     dword ptr [ebp-4],ecx
00a3005e 833d78318e0000  cmp     dword ptr ds:[8E3178h],0
00a30065 7405            je      00a3006c
00a30067 e8a4c2c673      call    clr!JIT_DbgIsJustMyCode (7469c310)
00a3006c 90              nop

c:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication12\ConsoleApplication12\Program.cs @ 13:
00a3006d 8b0d88214603    mov     ecx,dword ptr ds:[3462188h] ("1")
00a30073 ff15b0378e00    call    dword ptr ds:[8E37B0h] (ConsoleApplication12.Program.Display(System.String), mdToken: 06000002)
00a30079 90              nop

c:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication12\ConsoleApplication12\Program.cs @ 14:
00a3007a e8b1a71b73      call    mscorlib_ni+0xa0a830 (73bea830) (System.Console.ReadLine(), mdToken: 06000995)
00a3007f 8945f8          mov     dword ptr [ebp-8],eax
00a30082 90              nop

c:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication12\ConsoleApplication12\Program.cs @ 15:
00a30083 8b0d8c214603    mov     ecx,dword ptr ds:[346218Ch] ("2")
00a30089 ff15b0378e00    call    dword ptr ds:[8E37B0h] (ConsoleApplication12.Program.Display(System.String), mdToken: 06000002)
00a3008f 90              nop

c:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication12\ConsoleApplication12\Program.cs @ 16:
00a30090 90              nop
00a30091 8be5            mov     esp,ebp
00a30093 5d              pop     ebp
00a30094 c3              ret

Proszę zwrócić uwagę na wywołanie metody Display:

00a30073 ff15b0378e00    call    dword ptr ds:[8E37B0h] (ConsoleApplication12.Program.Display(System.String), mdToken: 06000002)

Ustawiając teraz breakpoint na Display dostaniemy:

0:000> !bpmd ConsoleApplication12.exe ConsoleApplication12.Program.Display
Found 1 methods in module 004f2ed4...
MethodDesc = 004f37b4
Adding pending breakpoints...

Oznacza to, że 004f37b4 jest identyfikatorem Display. Spróbujmy wywołać !u 004f37b4, aby otrzymać kod:

0:000> !u 004f37b4
Not jitted yet

Widzimy wyraźnie, że jest to niemożliwe – drugi etap kompilacji Display nie nastąpił jeszcze. Jeśli pozwolimy na pierwsze wykonanie tej metody (polecenie “g”) to będziemy mogli zobaczyć kod za pomocą tego samego polecenia (!u 004f37b4):

005800d8 55              push    ebp
005800d9 8bec            mov     ebp,esp
005800db 50              push    eax
005800dc 894dfc          mov     dword ptr [ebp-4],ecx
005800df 833d78314f0000  cmp     dword ptr ds:[4F3178h],0
005800e6 7405            je      005800ed
005800e8 e823c21174      call    clr!JIT_DbgIsJustMyCode (7469c310)
005800ed 90              nop

c:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication12\ConsoleApplication12\Program.cs @ 24:
005800ee 8b4dfc          mov     ecx,dword ptr [ebp-4]
005800f1 e84601fa72      call    mscorlib_ni+0x34023c (7352023c) (System.Console.WriteLine(System.String), mdToken: 060009a3)
005800f6 90              nop

c:\Users\Piotr\Documents\Visual Studio 2013\Projects\ConsoleApplication12\ConsoleApplication12\Program.cs @ 25:
005800f7 90              nop
005800f8 8be5            mov     esp,ebp
005800fa 5d              pop     ebp
005800fb c3              ret

Możemy również napisać mały benchmark, aby pokazać, że faktycznie zachodzi JIT:

class Program
{
   static void Main(string[] args)
   {
       Stopwatch stopWatch = Stopwatch.StartNew();
       Display("1");
       Console.WriteLine(stopWatch.ElapsedTicks);
       
       stopWatch.Restart();
       Display("2");
       Console.WriteLine(stopWatch.ElapsedTicks);
   }

   private static void Display(string text)
   {
       Console.WriteLine(text);
   }
}

Na ekranie pojawi się:

image

Widać, że drugie wywołanie jest wyraźnie szybsze ponieważ kod jest już po JIT.

Polecam zapoznanie się z narzędziem WinDbg, szczególnie dla osób, które muszą naprawiać błędy występujące wyłącznie na produkcji lub w innym specyficznym środowisku.

Visual Studio–kilka skrótów ułatwiających pracę

Posted August 20th, 2014 by Piotr Zieliński
Categories: Patterns & Practices, Visual Studio

1. Załóżmy, że mamy klasę z wieloma polami, np.:

public struct Employee
{
   internal string Field1;
   internal string Field2;
   internal string Field3;
   internal string Field4;
   internal string Field5;
   internal string Field6;
}

Następnie chcemy zmienić modyfikator internal na public. Naturalne podejście byłoby zmodyfikowanie wszystkich wywołań jeden po drugim. Inne rozwiązanie to CTRL+H i automatycznie zastąpienie. W zależności od konkretnej klasy, może być to wygodne lub nie. Trzeba być uważnym jednak, aby wszystkich wystąpień w pliku nie zamienić(jeśli np. chcemy tylko kilka z nich zaktualizować).

W VS możemy nacisnąć klawisz ALT, który umożliwi nam pionowe zaznaczenie tzn.:

image

Następnie możemy zmodyfikować tekst do public i zostanie to odzwierciedlone we wszystkich zaznaczonych liniach tzn.:

image

To jest dość stara funkcja Visual Studio, ale niezbyt popularna, a czasami potrafi ułatwić życie.

2. Jeśli użyjemy CTRL, a potem – (myślnik), to kursor zostanie przeniesiony do ostatnio używanej linii Przydatne, gdy skaczemy gdzieś dalej w pliku (aby zmodyfikować np. parametr wejściowy), a potem chcemy z powrotem powrócić bez używania myszki. Analogicznie możemy skorzystać z kombinacji CTRL, Shift, – aby przejść do przodu.

3. Generalnie zasada jest taka, że im mniej używamy myszki, tym szybciej jesteśmy w stanie pisać kod. Ciekawym skrótem jest CTRl+X, który usuwa całą linie. Jeśli zaznaczymy ręcznie linie, to usunięta zostanie wyłącznie sama linia, bez znaku nowej linii. Na przykład, jeśli mamy kursor na polu Field3 to naciśnięcie CTRL+X, spowoduje usunięcie go wraz ze znakiem nowej linii, co poskutkuje:

public struct Employee
{
   public string Field1;
   public string Field2;
   public string Field4;
   public string Field5;
}

Jeśli z kolei cała linia byłoby zaznaczona, to znak nowej linii zostanie pozostawiony bez zmian:

public struct Employee
{
   public string Field1;
   public string Field2;

   public string Field4;
   public string Field5;
}

4. Jeśli chcemy przejść do innego aktywnego pliku (bez używania myszki) możemy użyć kombinacji CTRL+Tab, co wyświetli następujące okno:

image

5. Shift+Alt+Enter – szybkie przełączanie się między FULLSCREEN a normalnym trybem. Przydatne, gdy mamy dużo kodu i nie chcemy szukać tej opcji w menu.

6. Jeśli chcemy przesunąć daną linię w dół albo w górę również nie musimy używać myszki. Wystarczy nacisnąć alt i strzałkę w górę (up) lub w dół (down).

7. Zoom chyba jest dość oczywisty w VS. Wystarczy nacisnąć ctrl i poruszać kółkiem myszki, a rozmiar czcionki automatycznie będzie modyfikowany (tak jak to w większości przeglądarkach internetowych).

image

image

8. Pamiętam, że kiedyś aby sformatować kod w VS, używałem zwykle zamknięcia klamr itp. Na szczęście istnieje skrót, który to zrobi dla nas w dowolnym momencie. Załóżmy, że mamy źle sformatowany kod tzn.:

image

W menu głównym możemy przejść do Edit->Advanced->Format Document:

image

Warto również zapamiętać pokazane skróty klawiszowe – klikanie po menu jest zdecydowanie zbyt powolne…

9. Przewijanie bez myszki i zachowanie pozycji kursora – CTRL + UP albo DOWN.

10. Bardzo dużo osób używa resharper’a. Często zamiast korzystać z Solution Explorer, klikamy CTRL+T, aby przejść do konkretnej klasy. Niestety, czasami mamy tak skomplikowaną strukturę folderów, że nie mamy pojęcia gdzie znaleziony plik przez Resharper znajduje się. A co jeśli chcemy przenieść dany plik do innego folderu? Musimy jakąś zlokalizować dany element w Solution Explorer. W VS możemy kliknąć na Sync With Active Document w SE:

image

Mam na myśli tą ikonę dwóch strzałek. Wtedy automatycznie zostaniemy przeniesieni do danego pliku w Solution Explorer:

image

11. Peek Definition. To nowa opcja w VS o której pisałem tutaj. Wspominam o niej ponownie, ponieważ moim zdaniem jest niedoceniana i przez to nie zawsze programiści wyrabiają sobie odpowiedni nawyk z nowym narzędziem.

IL Assembly: Wywoływanie metod

Posted August 17th, 2014 by Piotr Zieliński
Categories: C#, Patterns & Practices

Kiedyś na blogu już wspomniałem o różnych sposobach wywoływania metod przez CLR. Myślę jednak, że warto przypomnieć sobie te informacje jeśli omawiamy już język IL. W IL istnieją trzy sposoby na wykonanie metod:

  1. call
  2. callvirt
  3. calli

Najważniejsze to te dwie pierwsze. Instrukcja callvirt służy, jak sama nazwa wskazuje, do wykonywania metod wirtualnych. Rozważmy przykład:

public class Employee
{
   public virtual void Print()
   {
       
   }
}

public class Manager : Employee
{
   public override void Print()
   {
       base.Print();
   }
}
class Program
{        
   private static void Main()
   {
       Employee employee=new Manager();
       employee.Print();
   }
}

Wygenerowany IL będzie wyglądać następująco:

IL_0001: newobj instance void ConsoleApplication11.Manager::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: callvirt instance void ConsoleApplication11.Employee::Print()

Widzimy, że callvirt jest używany tutaj, aby wykonać metodę Print:

IL_0008: callvirt instance void ConsoleApplication11.Employee::Print()

Jeśli przyjrzymy się powyższej instrukcji, zobaczymy, że w żaden sposób nie przekazuje ona referencji na której chcemy wykonać Print. Wspomniana metoda (Print) nie jest statyczna więc musimy jakoś mieć obiekt this.

Aby to zrozumieć, przejdźmy kilka instrukcji wyżej. Najpierw tworzymy instancje obiektu Manager:

IL_0001: newobj instance void ConsoleApplication11.Manager::.ctor()

Instrukcja newobj umieści nową instancję na stosie (co nie powinno być już zaskoczeniem). Następnie standardowo umieszczamy właśnie stworzoną wartość ze stosu w zmiennej lokalnej, a potem znów ją ładujemy na stos:

IL_0006: stloc.0
IL_0007: ldloc.0

Zmienne lokalne z kolei, zdefiniowane są następująco:

.locals init (
    [0] class ConsoleApplication11.Employee employee
)

Innymi słowy, callvirt weźmie wartość ze stosu i użyje jej jako this. callvirt potrafi wyznaczyć, która metoda powinna być wykonana za pomocą virtual method table. Z tego względu, dla wszystkich metod wirtualnych konieczne jest użycie callvirt a nie call. Instrukcja call nie korzysta z virtual method table, stąd nie wiadomo byłoby, która metoda powinna być wykonana.

Ponadto callvirt wyrzuca kilka intersujących wyjątków:

1. NullReferenceException – sprawdzane jest, czy obiekt na którym wykonujemy metodę ma jakąś wartość. W praktyce oznacza to, że następujący kod wyrzuci NullReferenceException:

Employee employee = null;
employee.Print();

Skutek:

image

Dla większości programistów jest to oczywiste, ale jak zobaczymy później, nie powinno to być takie naturalne, że wywołanie obiektu na NULL wyrzuca wyjątek.

2.  SecurityException – brak uprawnień

3. MissingMethodException

Kolejna instrukcja call służy do wykonania niewirtualnych metod. Klasycznym przykładem są metody statyczne:

public class Employee
{
   public virtual void Print()
   {
       
   }
   public static void StaticPrint()
   {
       
   }
}

class Program
{        
   private static void Main()
   {
       Employee.StaticPrint();
   }
}

IL:

IL_0001: call void ConsoleApplication11.Employee::StaticPrint()

Tutaj bardzo ważna uwaga. Call nie sprawdza czy referencja jest równa NULL. W przypadku metod statycznych, nie ma to znaczenia, ale co z niewirtualnymi instance methods?

C#:

public class Employee
{
   public void InstancePrint()
   {
       
   }
}
class Program
{        
   private static void Main()
   {
       Employee employee = null;
       employee.InstancePrint();
   }
}

IL:

IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: callvirt instance void ConsoleApplication11.Employee::InstancePrint()

I tutaj niespodzianka… Wywołujemy niestatyczną metodę a korzystamy z callvirt. Dlaczego? W końcu nie potrzebujemy korzystać z virtual table method. Twórcy CLR zdecydowali jednak się na taki krok, aby uniknąć sytuacji, gdzie wykonujemy metodę na NULL i nie powoduje to wyjątku. Byłoby to bardzo nieintuicyjne zachowanie. Niestety jednak, istnieją sytuacje, w których nie unikniemy tego problemu.

Metody rozszerzające są klasami statycznymi, a więc będą na pewno wykonane przez call. Niestety operują one czasami na klasycznych obiektach. Może zdarzyć się, że wykonamy metodę na referencji wskazującym na NULL. Przykład:

public class Employee
{
   public string Text = "Hello World";
}

static class EmployeeExtensions
{
   public static void Print(this Employee employee)
   {
       Console.WriteLine("Ten kod wykona sie bez problemu");
       Console.WriteLine(employee.Text);
   }
   
}
class Program
{        
   private static void Main()
   {
       Employee employee = null;
       employee.Print();
   }
}

Powyższy kod wywoła Print bez wyjątku. Nie jest sprawdzane czy obiekt równa się NULL. Dopiero próba dostępu do Text spowoduje wyjątek:

image

Trzeba mieć to na uwadze bo może spowodować to nieoczekiwane efekty w aplikacji.

IL do powyższego kodu:

IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: call void ConsoleApplication11.EmployeeExtensions::Print(class ConsoleApplication11.Employee)
IL_0009: nop
IL_000a: ret

Widać wyraźnie, że call został użyty. Podsumowując, call jest dla statycznych metod a callvirt dla instance-methods (nawet tych niewirtualnych!).

Póki co, zajęliśmy się wyłącznie metodami bez parametrów i wartości zwracanej. Prześledźmy następujący kod:

public class Employee
{
   public string Print(string text)
   {
       Console.WriteLine(text);

       return text;
   }
}

class Program
{        
   private static void Main()
   {
       Employee employee = new Employee();
       employee.Print("Hello World");
   }
}

IL:

IL_0001: newobj instance void ConsoleApplication11.Employee::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldstr "Hello World"
IL_000d: callvirt instance string ConsoleApplication11.Employee::Print(string)

Po kolei:

1. 0001 – tworzymy instancję obiektu

2. 0006 – przechowujemy obiekt w zmiennej lokalnej

3. 0007 – umieszczamy zmienną lokalną na stosie

4. 0008 – przechowuje na stosie napis “Hello World”

5. 000d – wywołujemy (callvirt) metodę Print. Na stosie zatem będziemy mieli obiekt this i parametr wejściowy “Hello World”.

Nie powinno budzić wątpliwości fakt, że parametry wejściowe, jak wszystko inne umieszczane są na stosie. Tak samo wynik (wartość zwracana) jest umieszczona na stosie. Powyższy fragment kodu nie pokazuje tego, ale return tak naprawdę umieści argument na stosie, który potem można zdjąć i wyświetlić jak każdą inną wartość.

Przejdźmy teraz do ciała metody Print, aby przekonać się jak argumenty są używane:

IL_0001: ldarg.1
IL_0002: call void [mscorlib]System.Console::WriteLine(string)
IL_0007: nop
IL_0008: ldarg.1
IL_0009: stloc.0
IL_000a: br.s IL_000c

IL_000c: ldloc.0
IL_000d: ret

ldarg załaduje parametr wejściowy o określonym indeksie na stos. Z kolei ret zwraca wartość i wykonanie. Reszta instrukcji powinna być znana (w debug trochę śmieci jest wygenerowanych). Ret zwraca wykonanie, ale sami powinniśmy umieścić zwracaną wartość na stosie (tutaj robimy to za pomocą ldloc.0, w której znajduje się po prostu parametr wejściowy wcześniej załadowany). Gdy funkcja zwraca void, wtedy po prostu nic nie umieszczamy na stosie.

Instrukcja calli jest najrzadziej spotykana i służy do wykonywania metod w sposób pośredni – przez wskaźnik (interop). Innymi słowy,  calli wywołuje metodę, która jest określona jako wskaźnik znajdujący się na stosie. Poprzednie instrukcje wywoływały jawnie konkretne metody.

Klasa ExceptionDispatchInfo w .NET 4.5

Posted August 14th, 2014 by Piotr Zieliński
Categories: C#, Patterns & Practices

W wersji .NET 4.5 pojawiła się klasa ExceptionDispatchInfo, która nie jest zbyt znana. W większości przypadków nie jest na szczęście potrzebna, ale np. w wielowątkowości może ułatwić bardzo specyficzne zadania wyrzucania i filtrowania wyjątków.

Klasyczny przykład użycia to zebranie w kolekcji różnych wyjątków, a następnie wyrzucenie ich ponownie (prawdopodobnie po nałożeniu odpowiedniego filtra) w jakimś innym miejscu. Taki scenariusz to jest dokładnie sytuacja w której klasa została użyta w TPL i w konstrukcji await\async.

Załóżmy, że mamy kilka metod:

private static  void Throw()
{
  Throw2();
}

private static void Throw2()
{
  Throw3();
}

private static void Throw3()
{
  throw new NotImplementedException();
}

Chcemy wywoływać Throw, która następnie wywoła kilka metod pod rząd, wywołując na końcu wyjątek. Sytuacja wielowątkowa może wyglądać następująco:

var exceptions = new ConcurrentBag<ExceptionDispatchInfo>();

ThreadPool.QueueUserWorkItem(_ =>
{
 try
 {
     Throw();
 }
 catch (Exception ex)
 {
     ExceptionDispatchInfo exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
     exceptions.Add(exceptionDispatchInfo);
 }
});

Jak widzimy, w kodzie tylko zbieramy wyjątki i nie wyrzucamy ich od razu. Taka sytuacja ma często miejsce w TPL, na przykład w przypadku równoległych pętli.

ExceptionDispatchInfo ma metodę Capture, która owinie odpowiednio wyjątek. Dzięki temu nie stracimy informacji o StackTrace.

Na końcu, w kodzie zbiorczym, chcemy wyjątek wyrzucić ponownie, ale koniecznie z zachowaniem oryginalnego stackstrace. W praktyce prawdopodobnie dokonujemy w tym miejscu również jakiś filtrów. Najprostszy przykład może wyglądać następująco:

foreach (ExceptionDispatchInfo exceptionDispatchInfo in exceptions)
{

 try
 {
     exceptionDispatchInfo.Throw();
 }
 catch (Exception ex)
 {
     Console.WriteLine("{0}", ex);
 }
}

ExceptionDispatchInfo zatem zawiera dwie najważniejsze metody:

  1. Capture – przechowanie wyjątku
  2. Throw – wyrzucenie go podobnie

Wygenerowany wyjątek będzie zawierał stary (oryginalny) stacktrace:

image

Innymi słowy, jeśli chcemy wyrzucić wyjątek ponownie w innym miejscu, bez modyfikacji StackTrace (np. poprzez WrappedException), możemy skorzystać z przedstawionej klasy.

IL Assembly: instrukcje warunkowe i pętle

Posted August 8th, 2014 by Piotr Zieliński
Categories: C#, Patterns & Practices

Pętle i warunki, naturalnie są z jednym z podstawowych elementów każdego programu. Języki wysokiego poziomu umożliwiają realizację tego za pomocą słów If, for, foreach,while. W językach niskiego poziomu, takich jak IL Assembly, wszystkie powyższe czynności wykonuje się za pomocą skoków warunkowych i bezwarunkowych. Działają one analogicznie do słowa kluczowego GOTO.

W IL, najprostsza instrukcja skoku to BR:

br jump1
// jakas logika tutaj
jump1:
//...

br nie zawiera żadnego warunku. Można ją porównać do goto – po prostu skacze do wskazanej lokalizacji.

Bardziej interesujące są brtrue oraz brfalse. W zależności czy wartość jest true lub false, skaczą do wskazanego miejsca. Rozważmy kod c#:

bool flag = true;
if (flag)
{
 Console.WriteLine("true");
}
else
{
 Console.WriteLine("false");
}

Wygenerowany IL to:

IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldc.i4.0
IL_0005: ceq
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: brtrue.s IL_001a

IL_000c: ldstr "true"
IL_0011: call void [mscorlib]System.Console::WriteLine(string)
IL_0018: br.s IL_0027

IL_001a: nop
IL_001b: ldstr "false"
IL_0020: call void [mscorlib]System.Console::WriteLine(string)
IL_0027: ret

Jeśli któraś z instrukcji jest niezrozumiała, zapraszam do poprzednich wpisów o IL na moim blogu. Wszystkie z powyższych instrukcji powinny być już zrozumiałe. Tak czy inaczej, spróbujmy rozszyfrować tą wiązankę.

1. IL_0001 – załadowanie wartości jeden na stos.

2. IL_0002 – zdjęcie wartości (1 – true) ze stosu i umieszczenie jej w zmiennej lokalnej o indeksie zero (czyli flag).

3. IL_003 – załadowanie zmiennej lokalnej na stos.

4. IL_004 – umieszczenie wartości 0 (false) na stosie.

5. IL_005 – ceq to instrukcja opisana w poprzednim wpisie. Porównuje dwie wartości i jeśli są one równe, cyfra 1 jest umieszczana na stosie, w przeciwnym wypadku 0. W naszej sytuacji będzie to porównanie jeden  z zero (1==0) co poskutkuje, że na stosie pojawi się 0.

6. IL_007 – zapisanie wartości ze stosu (w naszym przypadku zero) do zmiennej lokalnej o indeksie 1. Jest to automatycznie wygenerowana zmienna przez kompilator. Zaglądając do deklaracji przekonamy się o tym:

.locals init (
    [0] bool flag,
    [1] bool CS$4$0000
)

7. IL_008 – analogiczna sytuacja – umieszczenie zmiennej na stosie.

8. IL_009 – wreszcie mamy naszą instrukcję warunkową brtrue.s IL_001a. Jeśli wartość na stosie jest true, skoczymy do IL_001a, czyli:

IL_001a: nop
IL_001b: ldstr "false"
IL_0020: call void [mscorlib]System.Console::WriteLine(string)

W naszym przypadku, na stosie jest zero, zatem nie zostanie wykonany skok w tym momencie, a wykonamy:

IL_000c: ldstr "true"
IL_0011: call void [mscorlib]System.Console::WriteLine(string)

IL_0018: br.s IL_0027

Na końcu jednak widzimy br. Oczywiście, chcemy ominąć fragment kodu, który wykonuje gałąź else, stąd skaczemy do IL_007.

Jedyną instrukcją o której nie wspomniałem to nop. Na razie wystarczy wiedzieć, że ona nic nie robi (dosłownie, Do Nothing). W tym przypadku, służy wyłącznie jako miejsce skoku. Więcej o niej napiszę kiedy indziej.

Pozostałe instrukcje skoku to: beq, bne, blt, ble, bgt, bge. Analogicznie, w zależności od wyniku porównania, skaczą do podanego miejsca. Przykład c#:

int five = 5;
int three = 3;

if (five>three)
{
 Console.WriteLine("true");
}
else
{
 Console.WriteLine("false");
}

IL:

IL_0000: ldc.i4.5
IL_0001: stloc.0
IL_0002: ldc.i4.3
IL_0003: stloc.1
IL_0004: ldloc.0
IL_0005: ldloc.1
IL_0006: ble.s IL_0013

IL_0008: ldstr "true"
IL_000d: call void [mscorlib]System.Console::WriteLine(string)
IL_0012: ret

IL_0013: ldstr "false"
IL_0018: call void [mscorlib]System.Console::WriteLine(string)
IL_001d: ret

Warto zauważyć, że przedstawione instrukcje właściwie kończą się sufiksem .s.  Sufiks oznacza short i korzysta się takich instrukcji gdy wskaźnik skoku (przesunięcia) można zapisać za pomocą jednego bajta. Innymi słowy, jeśli nie trzeba skakać zbyt daleko w IL to lepiej zapisać przesunięcie w jednobajtowej liczbie, co jest oszczędnością i wpływa na wydajność.

IL, jako, że jest językiem niskopoziomowym nie posiada specjalnych poleceń dla pętli. Wykonuje się je po prostu za pomocą skoków. Jeśli należy wykonać drugą iterację, skaczę się z powrotem na początek.

Przykład c# while:

int i = 0;

while (i<5)
{
 i++;
}

IL:

IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: br.s IL_000b
// loop start (head: IL_000b)
IL_0005: nop
IL_0006: ldloc.0
IL_0007: ldc.i4.1
IL_0008: add
IL_0009: stloc.0
IL_000a: nop

IL_000b: ldloc.0
IL_000c: ldc.i4.5
IL_000d: clt
IL_000f: stloc.1
IL_0010: ldloc.1
IL_0011: brtrue.s IL_0005
// end loop

IL_0013: ret

Jeszcze kilka ważnych rzeczy z IL mam zamiar opisać w kolejnych postach. Bazując jednak wyłącznie na tym i ostatnich wpisach, wiele można zrozumieć analizując kod z Reflector czy ILSpy.

IL assembly: Załadowanie zmiennych lokalnych + operatory

Posted August 5th, 2014 by Piotr Zieliński
Categories: C#, Patterns & Practices

W poprzednim poście opisałem jak działa stos w IL. Wiemy, że jest on kluczowy dla wszelkich operacji. Jedną z ważniejszych instrukcji, wprowadzonych w tamtym poście jest ldc.i4. Służy ona do załadowania liczby 4 bajtowej na stos.
Oprócz niej, istnieje wiele innych instrukcji, które pełnią analogiczną rolę. Rozważmy następujący kod c#:

float floatNumber = 533.3f;
double doubleNumber = 5454.14;
string text = "text";
int[] array = new int[] {21, 3};

Console.WriteLine(floatNumber);
Console.WriteLine(doubleNumber);
Console.WriteLine(text);

Float zostanie załadowany za pomocą:
ldc.r4 533.3

Instrukcja analogiczna do wspomnianej ldc.i4. Cyfra „4” oznacza liczbę bajtów, a przedrostek „r” lub „i” kolejno real (zmiennoprzecinkowa) i integer (całkowita).

Dla double będzie to ldc.r8, ponieważ jest to liczba 8-bajtowa. String z kolei ma osobną instrukcję ldstr:

ldstr "text"

To jeszcze nie wszystko. W IL istnieje wiele skrótów. Oczywiście programistów c# zwykle takie szczegóły nie interesują. Ułatwia to jednak czytanie kodu IL, kiedy pracujemy nad jakąś optymalizacją albo gdy po prostu aplikacja nie działa poprawnie.

W celu zaprezentowania skrótów do powyższych instrukcji spójrzmy na:

int minusOne = -1;
int plusOne = 1;
bool falseVaue = false;
bool trueValue = true;

object objectNull = null;

Powyższe wartości maja specjalne instrukcje. Zamiast np. pisać ldc.i4 -1 mamy ldc.i4.m1. Podsumowując, powyższy kod wygeneruje kolejno:

•    ldc.i4.m1 – załadowanie -1

•    ldc.i4.1 – załadowanie +1

•    ldc.i4.0 – załadowanie false (Boolean)

•    ldc.i4.1 – załadowanie true (Boolean)

•    ldnull – załadowanie null

W dokumentacji znajdziemy wiele innych skrótów, ale są one analogiczne do powyższych i dość oczywiste.

Myślę, że przy okazji stosu, warto również wspomnień o:

•    pop – instrukcja zdejmuje element ze stosu

•    dup – instrukcja duplikujecie element znajdujący się na górze stosu.

Funkcja pop jest dość oczywista i po prostu usunie wartość ze stosu. Najprostszy kod, obrazujący pop to:

int a = 5;
a.ToString();
int b = 5;

Wywołanie ToString spowoduje umieszczenie wyniku na stosie. Nie jest on nigdzie używany, zatem należy go usunąć potem (przed int b=5) ze stosu:

ldc.i4.5
stloc.0
ldloca.s a
call instance string [mscorlib]System.Int32::ToString()
pop

Instrukcja dup z kolei ma na celu optymalizację. Bardzo często musimy mieć duplikat wartości na stosie. Na przykład, jeśli chcemy tą samą liczbę umieścić w zmiennej lokalnej i wykonać jakaś operacje na niej. Instrukcja stloc (umieszczenie wartości w zmiennej lokalnej) zdejmuje również wartość ze stosu, a wszelkie operacje operują wyłącznie na stosie.

W poprzednim wpisie pokazałem również instrukcje add. Oczywiście istnieje szereg innych operacji tzn.:

  • 1.    neg – negacja (!)
  • 2.    sub – odejmowanie
  • 3.    mul – mnożenie
  • 4.    div – dzielenie
  • 5.    rem – reszta z dzielenia
  • 6.    shl – przesuniecie o jeden bit w lewo
  • 7.    shr- analogicznie w prawo
  • 8.    operatory bitowe: and, or, xor, not

Jeśli mamy dwie wartości (a,b) na stosie, wykonanie odpowiedniej instrukcji umieści cyfrę 1 albo 0, w zależności od operatora i relacji argumentów. Dostępne instrukcje to: clt, cle, cgt, cge, ceq, cne.

Wprowadzenie do evaluation stack

Posted August 2nd, 2014 by Piotr Zieliński
Categories: C#, Patterns & Practices

Bardzo często na blogu poruszam tematykę c# internals. Bez nich, praktycznie niemożliwe jest pisanie optymalnego kodu. Jeśli ktoś np. nie wie jak async\await jest zaimplementowany wewnętrznie, bardzo łatwo może popełnić błędy podczas pisania kodu c#. Niedawno ktoś zasugerował mi, abym wyjaśnił bardziej IL. Bardzo często wklejam fragmentu kodu z Reflector’ora i nie wyjaśniam szczegółów.

Z tego względu, przez kilka kolejnych wpisów zajmiemy się CLR internals oraz IL.

Na początek podstawowe pytanie, co to jest IL? Jest to skrót od  Intermediate Language. Kompilując kod C# czy VB, nie otrzymujemy kodu maszynowego, specyficznego dla konkretnej architektury. IL to język pośredni, który zostanie dopiero zamieniony w konkretny kod maszynowy przez JIT. Umożliwia to pisanie kodu, który nie jest zależny od konkretnej platformy\architektury. Dzięki temu również, mogą być dokonywane różne optymalizacje zależne np. od konkretnego CPU.

Bardzo często mylony jest IL z IL Assembly. IL to tak naprawdę kod binarny, którego człowiek nie jest w stanie przeczytać\zrozumieć. Z kolei IL Assembly to język niskiego poziomu (assembler). W skrócie będę pisał na blogu IL pokazując różne fragmenty kodu. Ale należy mieć na uwadze, że tak naprawdę chodzi IL Assembly… Nie da się wykonać bezpośrednio IL Assembly, musi on być potem z powrotem zamieniony do IL.

Dzisiaj zajmiemy jednym z ważniejszych elementów IL, a mianowicie stosem (konkretniej evaluation stack). IL, w przeciwieństwie do wielu innych jeżyków jest oparty na stosie a nie na rejestrach. Wszystkie operacje wykonujemy, umieszczając pewne dane na stosie, a potem wykonując różne instrukcje.

W skrócie, IL nie posiada rejestrów, ale stos jak i m.in. zmienne lokalne. Nie lubię zbyt wiele teorii, więc zacznijmy od przykładu c#:

static  void Main(string[] args)
{
  int a = 5;
  int b = 6;
  int c = a + b;
}

W tym prostym programie, mamy 3 zmienne lokalne. Pierwsza z nich przechowuje stałe a kolejna („c”) będzie zawierać sumę dwóch wcześniejszych zmiennych. Wygenerowany IL Assembly, będzie wyglądać, więc następująco:

.locals init (
   [0] int32 num,
   [1] int32 num2,
   [2] int32 num3)
L_0001: ldc.i4.5 
L_0002: stloc.0 
L_0003: ldc.i4.6 
L_0004: stloc.1 
L_0005: ldloc.0 
L_0006: ldloc.1 
L_0007: add 
L_0008: stloc.2 

Najpierw widzimy deklaracje zmiennych lokalnych:

.locals init (
   [0] int32 num,
   [1] int32 num2,
   [2] int32 num3)

To było proste do wywnioskowania. Kolejne instrukcje, bez znajomości internali są ciężkie prawdopodobnie do rozszyfrowania. Stos służy do wykonywania operacji. Aby dodać dwie liczby, należy je najpierw umieścić na stosie. Analogicznie, nie operuje się bezpośrednio na zmiennych lokalnych. Jeśli chcemy przechować liczbę w zmiennej lokalnej, najpierw należy ją umieścić na stosie, a potem zdjąć ją, umieszczając w konkretnej zmiennej lokalnej.

Spróbujmy przedstawić działanie powyższego kodu. W celu załadowania jakiejkolwiek wartości do zmiennej lokalnej, należy najpierw umieścić ją na stosie. Z tego względu stos na początku będzie wyglądać następująco:

clip_image002

Zmienne lokalne na tym etapie są puste. Powyższą operację, wykonuje się instrukcją ldc.i4 – przechowuje ona liczbę 4-bajtową (Int32) na stosie.

Kolejna instrukcja to stloc.0. Zdejmie ona wartość (5) ze stosu i umieści ją w pierwszej zmiennej lokalnej. Następnie mamy dwie kolejne, analogiczne instrukcje dla cyfry 6. Najpierw umieszczamy ją na stosie, a potem zdejmujemy, wrzucając do zmiennej lokalnej o indeksie jeden:

L_0003: ldc.i4.6 
L_0004: stloc.1 

Stos jest pusty na tym etapie, a dane znajdują się w zmiennych lokalnych (a,b). W celu ich dodania, musimy znów umieścić je na stosie:

L_0005: ldloc.0 
L_0006: ldloc.1 

Ldloc to instrukcja analogiczna do stloc. Jak wiemy, stloc zdejmuje wartość ze stosu, umieszczając ją w zmiennej lokalnej, a z kolei ldloc załaduje zmienną lokalną na stos. Oznacza to, że stos po tych dwóch instrukcjach będzie wyglądać następująco:

clip_image002[5]

Następnie wykonujemy add, która doda dwie wartości ze stosu i przechowa wynik również na nim:

L_0007: add

Co w wyniku da:

clip_image002[7]

Ostatnia linia już nie powinna być zaskoczeniem:

L_0008: stloc.2

Spowoduje, to zdjęcie wartości ze stosu i umieszczenie jej w lokalnej zmiennej o indeksie 2 (czyli zmienna o nazwie c).

Na pierwszy wpis wystarczy… Podsumowując jednak instrukcje z dzisiaj:

  1. stloc – zdejmuje wartość ze stosu i umieszcza ją we wskazanej przez indeks zmiennej lokalnej.
  2. ldloc – załaduje zmienną lokalną o wskazanym indeksie do stosu (push).
  3. add – sumuje dwie liczby (operuje na stosie).
  4. ldc.i4 – ładuje liczbę 4 bajtową (Int32) na stos.

Microsoft Fakes

Posted July 30th, 2014 by Piotr Zieliński
Categories: Testy

Istnieje wiele framework’ow ułatwiających izolację danych w testach jednostkowych. Nie opisuje ich na blogu, bo nie wiele od siebie różnią się . Microsoft Fakes jednak ma kilka ciekawych rzeczy i dlatego nim dzisiaj zajmiemy się.

Niestety jest dostępny on wyłącznie w wersji Visual Studio Ultimate. Pierwszą wyróżniającą go cechą jest możliwość izolacji metod statycznych, które oczywiście nie mogą być w łatwy sposób mock’owane.  Rozważmy klasyczny przykład:

public class Person
{
    public void Method()
    {
        if (DateTime.Now == new DateTime(2000, 1, 1))
        {
            // jakas logika
        } 
    }
}

Jak przetestować powyższy warunek? W jaki sposób, zasymulować, że DateTime.Now zwróci rok 2000? Przede wszystkim, ktoś może powiedzieć, że należy unikać statycznych wywołać i wszystko powinno być wstrzykiwane przez konstruktor. Taka osoba będzie miała oczywiście rację. Niestety w praktyce trzeba pracować również z legacy code i nie zawsze mamy wszystko zaprojektowane tak jak powinno to być.

Microsoft Fakes implementuje tzw. shims. W odróżnieniu od stub’ow oraz mock’ow, umożliwiają one zastąpienie dowolnej statycznej metody innym wywołaniem. Stub z kolei, jak dobrze wiemy, polega na implementacji po prostu odpowiedniego interfejsu lub klasy. Mock to rozszerzony stub o możliwość śledzenia wywołań.

Najlepiej to pokazać na przykładzie. Z kontekstowego menu wybieramy “Add Fake Assembly”:

image

Chcemy stworzyć shim dla DateTime, który znajduje się w System – dlatego, też najpierw zaznaczyliśmy tą bibliotekę. Po chwili zostaną wygenerowane specjalne biblioteki, zawierające szereg stub’ow, mock’ow i shim’ow. To jest kolejna różnica między Microsoft Fakes a innymi frameworkami – wszystkie typy są po prostu generowane jako zwykłe klasy.

Konfiguracja shim’a wygląda następująco:

using (ShimsContext.Create())
{
    ShimDateTime.NowGet = () => new DateTime(2000, 1, 1);

    var person = new Person();
    person.Method();
}

Nie ma w tym nic trudnego – zwykła lambda. Wygenerowane shim’y posiadają pewną konwencję w nazwach. Klasa jest poprzedzona słowem Shim (DateTime->ShimDateTime), z kolei właściwość kończy się Set albo Get (DateTime.Now->ShimDateTime.NowGet).

Ponadto, wywołanie musi być opatrzone w ShimContext bo skonfigurowana lambda może być tylko wykonywana w danym kontekście.

Po uruchomieniu kodu,  przekonamy się, że warunek zostanie spełniony ponieważ DateTime.Now zwróci teraz rok 2000. Analogicznie możemy tworzyć shim’y dla dowolnych typów – wystarczy kliknąć Add Fake Assembly dla danej biblioteki.

Cały wygenerowany kod można z łatwością zobaczyć w VS. Na przykład ShimDateTime wygląda następująco:

// Type: System.Fakes.ShimDateTime
// Assembly: mscorlib.4.0.0.0.Fakes, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0ae41878053f6703
// MVID: 6BEF2261-203B-4A2D-AABA-BC00EF51BEBA
// Assembly location: C:\Users\Piotr\documents\visual studio 2013\Projects\ConsoleApplication11\ClassLibrary1\FakesAssemblies\mscorlib.4.0.0.0.Fakes.dll

using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.QualityTools.Testing.Fakes.Shims;
using System;
using System.Diagnostics;
using System.Globalization;

namespace System.Fakes
{
  /// <summary>
  /// Shim type of System.DateTime
  /// </summary>
  [ShimClass(typeof (DateTime))]
  [DebuggerDisplay("Shim of DateTime")]
  [DebuggerNonUserCode]
  public sealed class ShimDateTime : ShimBase
  {
    /// <summary>
    /// Assigns the 'Current' behavior for all methods of the shimmed type
    /// </summary>
    public static void BehaveAsCurrent();
    /// <summary>
    /// Assigns the 'NotImplemented' behavior for all methods of the shimmed type
    /// </summary>
    public static void BehaveAsNotImplemented();
    /// <summary>
    /// Sets the shim of DateTime.op_Addition(DateTime d, TimeSpan t)
    /// </summary>
    public static FakesDelegates.Func<DateTime, TimeSpan, DateTime> AdditionOpDateTimeTimeSpan { [ShimMethod("op_Addition", 24)] set; }
    /// <summary>
    /// Assigns the behavior for all methods of the shimmed type
    /// </summary>
    public static IShimBehavior Behavior { set; }
    /// <summary>
    /// Sets the shim of DateTime.Compare(DateTime t1, DateTime t2)
    /// </summary>
    public static FakesDelegates.Func<DateTime, DateTime, int> CompareDateTimeDateTime { [ShimMethod("Compare", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.DateToTicks(Int32 year, Int32 month, Int32 day)
    /// </summary>
    public static FakesDelegates.Func<int, int, int, long> DateToTicksInt32Int32Int32 { [ShimMethod("DateToTicks", 40)] set; }
    /// <summary>
    /// Sets the shim of DateTime.DaysInMonth(Int32 year, Int32 month)
    /// </summary>
    public static FakesDelegates.Func<int, int, int> DaysInMonthInt32Int32 { [ShimMethod("DaysInMonth", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.DoubleDateToTicks(Double value)
    /// </summary>
    public static FakesDelegates.Func<double, long> DoubleDateToTicksDouble { [ShimMethod("DoubleDateToTicks", 40)] set; }
    /// <summary>
    /// Sets the shim of DateTime.op_Equality(DateTime d1, DateTime d2)
    /// </summary>
    public static FakesDelegates.Func<DateTime, DateTime, bool> EqualityOpDateTimeDateTime { [ShimMethod("op_Equality", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.Equals(DateTime t1, DateTime t2)
    /// </summary>
    public static FakesDelegates.Func<DateTime, DateTime, bool> EqualsDateTimeDateTime { [ShimMethod("Equals", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.FromBinary(Int64 dateData)
    /// </summary>
    public static FakesDelegates.Func<long, DateTime> FromBinaryInt64 { [ShimMethod("FromBinary", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.FromBinaryRaw(Int64 dateData)
    /// </summary>
    public static FakesDelegates.Func<long, DateTime> FromBinaryRawInt64 { [ShimMethod("FromBinaryRaw", 40)] set; }
    /// <summary>
    /// Sets the shim of DateTime.FromFileTime(Int64 fileTime)
    /// </summary>
    public static FakesDelegates.Func<long, DateTime> FromFileTimeInt64 { [ShimMethod("FromFileTime", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.FromFileTimeUtc(Int64 fileTime)
    /// </summary>
    public static FakesDelegates.Func<long, DateTime> FromFileTimeUtcInt64 { [ShimMethod("FromFileTimeUtc", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.FromOADate(Double d)
    /// </summary>
    public static FakesDelegates.Func<double, DateTime> FromOADateDouble { [ShimMethod("FromOADate", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.op_GreaterThan(DateTime t1, DateTime t2)
    /// </summary>
    public static FakesDelegates.Func<DateTime, DateTime, bool> GreaterThanOpDateTimeDateTime { [ShimMethod("op_GreaterThan", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.op_GreaterThanOrEqual(DateTime t1, DateTime t2)
    /// </summary>
    public static FakesDelegates.Func<DateTime, DateTime, bool> GreaterThanOrEqualOpDateTimeDateTime { [ShimMethod("op_GreaterThanOrEqual", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.op_Inequality(DateTime d1, DateTime d2)
    /// </summary>
    public static FakesDelegates.Func<DateTime, DateTime, bool> InequalityOpDateTimeDateTime { [ShimMethod("op_Inequality", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.IsLeapYear(Int32 year)
    /// </summary>
    public static FakesDelegates.Func<int, bool> IsLeapYearInt32 { [ShimMethod("IsLeapYear", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.op_LessThan(DateTime t1, DateTime t2)
    /// </summary>
    public static FakesDelegates.Func<DateTime, DateTime, bool> LessThanOpDateTimeDateTime { [ShimMethod("op_LessThan", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.op_LessThanOrEqual(DateTime t1, DateTime t2)
    /// </summary>
    public static FakesDelegates.Func<DateTime, DateTime, bool> LessThanOrEqualOpDateTimeDateTime { [ShimMethod("op_LessThanOrEqual", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.get_Now()
    /// </summary>
    public static FakesDelegates.Func<DateTime> NowGet { [ShimMethod("get_Now", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.ParseExact(String s, String[] formats, IFormatProvider provider, DateTimeStyles style)
    /// </summary>
    public static FakesDelegates.Func<string, string[], IFormatProvider, DateTimeStyles, DateTime> ParseExactStringStringArrayIFormatProviderDateTimeStyles { [ShimMethod("ParseExact", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.ParseExact(String s, String format, IFormatProvider provider)
    /// </summary>
    public static FakesDelegates.Func<string, string, IFormatProvider, DateTime> ParseExactStringStringIFormatProvider { [ShimMethod("ParseExact", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.ParseExact(String s, String format, IFormatProvider provider, DateTimeStyles style)
    /// </summary>
    public static FakesDelegates.Func<string, string, IFormatProvider, DateTimeStyles, DateTime> ParseExactStringStringIFormatProviderDateTimeStyles { [ShimMethod("ParseExact", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.Parse(String s)
    /// </summary>
    public static FakesDelegates.Func<string, DateTime> ParseString { [ShimMethod("Parse", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.Parse(String s, IFormatProvider provider)
    /// </summary>
    public static FakesDelegates.Func<string, IFormatProvider, DateTime> ParseStringIFormatProvider { [ShimMethod("Parse", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.Parse(String s, IFormatProvider provider, DateTimeStyles styles)
    /// </summary>
    public static FakesDelegates.Func<string, IFormatProvider, DateTimeStyles, DateTime> ParseStringIFormatProviderDateTimeStyles { [ShimMethod("Parse", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.SpecifyKind(DateTime value, DateTimeKind kind)
    /// </summary>
    public static FakesDelegates.Func<DateTime, DateTimeKind, DateTime> SpecifyKindDateTimeDateTimeKind { [ShimMethod("SpecifyKind", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.DateTime()
    /// </summary>
    public static FakesDelegates.Action StaticConstructor { [ShimMethod(".cctor", 40)] set; }
    /// <summary>
    /// Sets the shim of DateTime.op_Subtraction(DateTime d1, DateTime d2)
    /// </summary>
    public static FakesDelegates.Func<DateTime, DateTime, TimeSpan> SubtractionOpDateTimeDateTime { [ShimMethod("op_Subtraction", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.op_Subtraction(DateTime d, TimeSpan t)
    /// </summary>
    public static FakesDelegates.Func<DateTime, TimeSpan, DateTime> SubtractionOpDateTimeTimeSpan { [ShimMethod("op_Subtraction", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.TicksToOADate(Int64 value)
    /// </summary>
    public static FakesDelegates.Func<long, double> TicksToOADateInt64 { [ShimMethod("TicksToOADate", 40)] set; }
    /// <summary>
    /// Sets the shim of DateTime.TimeToTicks(Int32 hour, Int32 minute, Int32 second)
    /// </summary>
    public static FakesDelegates.Func<int, int, int, long> TimeToTicksInt32Int32Int32 { [ShimMethod("TimeToTicks", 40)] set; }
    /// <summary>
    /// Sets the shim of DateTime.get_Today()
    /// </summary>
    public static FakesDelegates.Func<DateTime> TodayGet { [ShimMethod("get_Today", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.TryCreate(Int32 year, Int32 month, Int32 day, Int32 hour, Int32 minute, Int32 second, Int32 millisecond, DateTime&amp; result)
    /// </summary>
    public static FakesDelegates.OutFunc<int, int, int, int, int, int, int, DateTime, bool> TryCreateInt32Int32Int32Int32Int32Int32Int32DateTimeOut { [ShimMethod("TryCreate", 40)] set; }
    /// <summary>
    /// Sets the shim of DateTime.TryParseExact(String s, String[] formats, IFormatProvider provider, DateTimeStyles style, DateTime&amp; result)
    /// </summary>
    public static FakesDelegates.OutFunc<string, string[], IFormatProvider, DateTimeStyles, DateTime, bool> TryParseExactStringStringArrayIFormatProviderDateTimeStylesDateTimeOut { [ShimMethod("TryParseExact", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.TryParseExact(String s, String format, IFormatProvider provider, DateTimeStyles style, DateTime&amp; result)
    /// </summary>
    public static FakesDelegates.OutFunc<string, string, IFormatProvider, DateTimeStyles, DateTime, bool> TryParseExactStringStringIFormatProviderDateTimeStylesDateTimeOut { [ShimMethod("TryParseExact", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.TryParse(String s, DateTime&amp; result)
    /// </summary>
    public static FakesDelegates.OutFunc<string, DateTime, bool> TryParseStringDateTimeOut { [ShimMethod("TryParse", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.TryParse(String s, IFormatProvider provider, DateTimeStyles styles, DateTime&amp; result)
    /// </summary>
    public static FakesDelegates.OutFunc<string, IFormatProvider, DateTimeStyles, DateTime, bool> TryParseStringIFormatProviderDateTimeStylesDateTimeOut { [ShimMethod("TryParse", 24)] set; }
    /// <summary>
    /// Sets the shim of DateTime.get_UtcNow()
    /// </summary>
    public static FakesDelegates.Func<DateTime> UtcNowGet { [ShimMethod("get_UtcNow", 24)] set; }
    /// <summary>
    /// Define shims for all instances members
    /// </summary>
    public static class AllInstances
    {
    }
  }
}

Microsoft Fakes to również stub’y.  Załóżmy, że mamy następujący interfejs:

public interface IPersonRepository
{
   void AddPerson(Person person);
   Person[] GetAll();
}

Następnie chcemy stworzyć stub, który dostarcza jakąś prostą implementacje powyższych metod. Najpierw klikamy “Add Fake Assembly” na referencji biblioteki, która zawiera powyższy interfejs. Zgodnie z konwencją, zostanie wygenerowana klasa StubIPersonRepository:

  [StubClass(X)]
  [DebuggerDisplay("Stub of IPersonRepository")]
  [DebuggerNonUserCode]
  public class StubIPersonRepository : StubBase<ConsoleApplication11.IPersonRepository>, ConsoleApplication11.IPersonRepository
  {
    /// <summary>
    /// Sets the stub of IPersonRepository.AddPerson(Person person)
    /// </summary>
    public FakesDelegates.Action<ConsoleApplication11.Person> AddPersonPerson;
    /// <summary>
    /// Sets the stub of IPersonRepository.GetAll()
    /// </summary>
    public FakesDelegates.Func<ConsoleApplication11.Person[]> GetAll;
  }

Jak widzimy, Microsoft Fakes oparty jest na delegatach. Jest to trochę inny model niż Moq, który bazował na proxy. W Microsoft Fakes, generowane są realne klasy, a potem za pomocą np. lambdy, możemy określić zachowanie poszczególnych metod:

var stub=new StubIPersonRepository();
var inMemoryRepository=new List<ConsoleApplication11.Person>();

stub.GetAll = () => inMemoryRepository.ToArray();
stub.AddPersonPerson = (person) => inMemoryRepository.Add(person);

Następnie możemy korzystać z powyższego stub’a, jak z normalnej klasy:

IPersonRepository personRepository = stub;
personRepository.AddPerson(new Person());

Framework implementuje również wzorzec obserwator, co w praktyce oznacza, że możemy korzystać również z mock’ow a nie wyłacznie z stub i dummy. Przykład:

var stub=new StubIPersonRepository();
var stubObserver = new StubObserver();

stub.InstanceObserver = stubObserver;

IPersonRepository personRepository = stub;
personRepository.AddPerson(new Person());
Person[] all = personRepository.GetAll();

StubObservedCall[] allCalls=stubObserver.GetCalls();

foreach (StubObservedCall call in allCalls)
{
 Console.WriteLine(call.StubbedMethod);
 Console.WriteLine(call.StubbedType);

 object[] arguments = call.GetArguments();

 foreach (object argument in arguments)
 {
     Console.WriteLine(argument);
 }
}

Niestety nie jest to zbyt proste w użyciu, dlatego na Microsoft Fakes należy patrzeć jako framework dostarczający głównie shim’y i stub’y a do mock’oq lepiej wykorzystać dobrze znany Moq. Na zakończenie warto zobaczyć wspomniany interfejs obserwatora:

[DebuggerNonUserCode]
public sealed class StubObserver : IStubObserver
{
    public void Clear();
    public StubObservedCall[] GetCalls();
    void IStubObserver.Enter(Type stubbedType, Delegate stubCall);
    void IStubObserver.Enter(Type stubbedType, Delegate stubCall, object arg1);
    void IStubObserver.Enter(Type stubbedType, Delegate stubCall, object arg1, object arg2);
    void IStubObserver.Enter(Type stubbedType, Delegate stubCall, object arg1, object arg2, object arg3);
    void IStubObserver.Enter(Type stubbedType, Delegate stubCall, params object[] args);
}

Własna implementacja await’era

Posted July 27th, 2014 by Piotr Zieliński
Categories: C#, Patterns & Practices

Wiele razy pisałem już o słowach async\await i z pewnością ułatwiają one życie. Dla przypomnienia rozważmy kod:

private async void DownloadAndSortAsync()
{
      int[] allNumbers = await DownloadNumbersAsync();
      int[] sortedNumbers = await SortNumbersAsync(allNumbers);            
      MessageBox.Show(string.Join(“,”,sortedNumbers));
}
private Task<int[]> DownloadNumbersAsync()
{
      return Task<int[]>.Factory.StartNew(() => DownloadNumbers());
}
 private Task<int[]> SortNumbersAsync(int[] numbers)
{
      return Task<int[]>.Factory.StartNew(() => SortNumbers(numbers));
}

Ciekawą możliwością jest implementacja własnego await’er,a który nie musi być związany z zadaniami (Task). Jako przykład, pokażę zaproponowany przez J.Richter awaiter dla zdarzeń. Załóżmy, że chcemy czekać na zdarzenie aż zostanie wysłane, ale nie chcemy jednocześnie blokować wątku.

Stwórzmy najpierw klasy, na których będziemy testować rozwiązanie:

public class MessageEventArgs : EventArgs
{
   public MessageEventArgs(string message)
   {
       Message = message;
   }
   public string Message { get; private set; }
}

public class MessageController
{
   public event EventHandler<MessageEventArgs> MessageReceived;

   public void SendMessage(string message)
   {
       if(MessageReceived!=null)
           MessageReceived(this,new MessageEventArgs(message));
   }
}

Prosty kontroler eksponujący zdarzenie. Kolejnym celem jest czekanie aż zdarzenie zostanie odebrane.  Innymi słowy, chcemy odbierać wiadomości w pętli i wykonać jakieś zadanie tzn.:

while(true)
{
    MessageEventArgs args = await _controller.MessageReceived;    
    Console.WriteLIne(args.Message);
}

Oczywiście powyższy kod nie zadziała ponieważ zdarzenie c# nie wspiera słowa kluczowego await.  Musimy najpierw zaimplementować interfejs INotifyCompletion:

/// <summary>
/// Represents an operation that schedules continuations when it completes.
/// </summary>
public interface INotifyCompletion
{
    /// <summary>
    /// Schedules the continuation action that's invoked when the instance completes.
    /// </summary>
    /// <param name="continuation">The action to invoke when the operation completes.</param><exception cref="T:System.ArgumentNullException">The <paramref name="continuation"/> argument is null (Nothing in Visual Basic).</exception>
    void OnCompleted(Action continuation);
}

Do zaimplementowania mamy tylko jedną metodę z interfejsu. OnCompleted przekaże nam tzw. continuationPoint. Po odebraniu zdarzenia właśnie tą delegatę będziemy wywoływać, aby powiadomić maszynę stanów await o zakończeniu operacji.

Najprostsza implementacja await’era wygląda następująco:

public class MessageEventAwaiter : INotifyCompletion
{
   private Action _continuationPoint;
   private MessageEventArgs _result;

   public void OnCompleted(Action continuation)
   {
       _continuationPoint = continuation;
   }

   public void EventHandler(object sender, MessageEventArgs messageEventArgs)
   {
       _result = messageEventArgs;
       if (_continuationPoint != null)
           _continuationPoint();
   }

   public MessageEventAwaiter GetAwaiter()
   {
       return this;
   }

   public MessageEventArgs GetResult()
   {
       return _result;
   }

   public bool IsCompleted
   {
       get { return _result != null; }
   }
}

W metodzie OnCompleted po prostu buforujemy continuationPoint, który zostanie przekazany nam przez state machine:

public void OnCompleted(Action continuation)
{
  _continuationPoint = continuation;
}

EventHandler to metoda, którą przekażemy zdarzeniu. To właśnie ona zostanie najpierw wywołana przez zdarzenie:

public void EventHandler(object sender, MessageEventArgs messageEventArgs)
{
  _result = messageEventArgs;
  if (_continuationPoint != null)
      _continuationPoint();
}

Innymi słowy, zdarzenie wywołuje metodę EventHandler, a ona z kolei powiadomi state machine za pomocą _continuationPoint.

Następnie mamy trzy metody, które są wymagane dla wsparcia słowa kluczowego await. Bez nich, kod po prostu by się nie skompilował:

public MessageEventAwaiter GetAwaiter()
{
  return this;
}

public MessageEventArgs GetResult()
{
  return _result;
}

public bool IsCompleted
{
  get { return _result != null; }
}

Nie są one zbyt skomplikowane, po prostu sprawdzają czy zadanie zostało już wykonane itp.

Tak naprawdę powyższy kod nie jeszcze idealny, ale spróbujmy go przetestować za pomocą:

var messageController = new MessageController();
var messageAwaiter=new MessageEventAwaiter();

Task.Factory.StartNew(() =>
{
 int i = 0;
 while (true)
 {
     Thread.Sleep(2000);
     messageController.SendMessage(i.ToString(CultureInfo.InvariantCulture));
     i++;
 }
});

messageController.MessageReceived+=messageAwaiter.EventHandler;

while (true)
{
 MessageEventArgs result = await messageAwaiter;
 Console.WriteLine(result.Message);
}

Najpierw tworzymy wątek, który co dwie sekundy wysyła zdarzenie, a potem w pętli odbieramy je za pomocą słowa kluczowego await. Gdybyśmy teraz uruchomili aplikację na ekranie zobaczylibyśmy wyłącznie pierwsze zdarzenie:

image

Dlaczego? Widzimy, że zadanie jest uznane za ukończone jak tylko _result jest ustawiony na jakaś wartość. Ma to miejsce już po pierwszym zdarzeniu.

Z tego wniosek, że musimy kolejkować zdarzenia:

public class MessageEventAwaiter : INotifyCompletion
{
   private Action _continuationPoint;
   private Queue<MessageEventArgs> _results = new Queue<MessageEventArgs>();

   public void OnCompleted(Action continuation)
   {
       _continuationPoint = continuation;
   }

   public void EventHandler(object sender, MessageEventArgs messageEventArgs)
   {
       _results.Enqueue(messageEventArgs);
       if (_continuationPoint != null)
           _continuationPoint();
   }

   public MessageEventAwaiter GetAwaiter()
   {
       return this;
   }

   public MessageEventArgs GetResult()
   {
       return _results.Dequeue();
   }

   public bool IsCompleted
   {
       get { return _results.Count > 0; }
   }
}

Teraz, zamiast przechowywać wyłącznie jedną wartość, mamy kolekcję danych. IsCompleted zwróci true, gdy cała kolejka będzie pusta. Na ekranie, po uruchomieniu, zobaczymy prawidłowe dane:

image

Kod wciąż nie jest idealny. W praktyce lepiej napisać generyczny awaiter, a nie taki, który obsługuje wyłącznie jeden typ zdarzenia. Kolejne możliwe ulepszenie to napisanie kodu thread-safe – powyższy przykład nie będzie działał w środowisku wielowątkowym, co zwykle ma miejsce w przypadku await. Tak czy inaczej, nie są to trudne zmiany do naniesienia, a myślę, że łatwiej było wytłumaczyć idee na przykładzie prostego przykładu a nie produkcyjnego kodu.

Debugowanie obiektów bez jawnej referencji

Posted July 24th, 2014 by Piotr Zieliński
Categories: Visual Studio

W Visual Studio istnieje pewna opcja, która umożliwia sprawdzenie wartości obiektu, do którego nie ma się jawnej referencji. Spójrzmy na następujący przykład:

class Program
{
   static void Main(string[] args)
   {        
       DoSomething();
   }

   private static void DoSomething()
   {
       var person=new Person();
       person.FirstName = "Piotr";
       person.LastName = "Zielinski";
   }
}

Jeśli ustawimy breakpoint w metodzie DoSomething naturalnie zobaczymy wartość obiektu person w oknie watch:

image

Jeśli jednak przejdziemy dalej, do funkcji main wtedy zmienna person będzie poza scope i watch nic nie pokaże:

image

Jest to naturalne zachowanie, ponieważ nie ma jawnej referencji do Person. Czasami jednak, mimo wszystko chcemy śledzić taki obiekt. Oczywiście w celu wykrycia memory leak służą profilery, ale czasami dostęp do referencji może być utrudniony a mimo to, chcemy mieć łatwy do niego dostęp. Łatwo wyobrazić sobie przykład z jakimś własnym kontenerem, gdzie nie ma publicznego dostępu do instancji. W VS można z menu podręcznego wybrać “Make Object ID”. Należy zatem kliknąć na zmiennej Person w watch i wybrać wspomnianą opcję:

image

Koło zmiennej wtedy pojawi się identyfikator w formie 1# :

image

Jest to tzw. słaba referencja. Możemy mieć do niej dostęp nawet w funkcji Main:

image

Wystarczy wpisać ją, jak każdą inną zmienną w oknie watch. Oczywiście, gdy GC zwolni zasoby, wtedy pojawi się NULL.  Obiekt wciąż jest widziany jako nieosiągalny i nie ma to wpływu na działanie GC.