Nie, nie zmieniłem pomysłu na prowadzenie bloga, znów będzie o C#. Przestrzenie nazw System.Reflection
i System.Reflection.Emit
udostępniają zestaw bardzo interesujących klas, służących do dynamicznego analizowania i generowania kodu. Pozwala to np. stworzyć uniwersalne narzędzia, służące do serializowania dowolnych, nieznanych wcześniej typów, czy też pisanie własnych kompilatorów i analizatorów kodu.
Mechanizm refleksji, chociaż potężny, jest niestety dość powolny, w związku z tym wskazane jest uzupełnianie go dynamicznym generowaniem kodu. W ten sposób wystarczy raz tylko wykonać kosztowną analizę obiektu, a potem można korzystać z zebranych wcześniej informacji, wywołując wygenerowane za pierwszym razem metody i klasy. Oczywiście, wyprodukowany w ten sposób kod można zapisać na dysku w formie zwyczajnych assembly, do późniejszego wykonania.
Korzystanie z przestrzeni nazw System.Reflection.Emit
wymaga niestety zapoznania się, chociaż pobieżnego, z CIL. Na szczęście SDK od Microsoftu zawiera dwa bardzo przydatne narzędzia. peverify.exe analizuje pliki assembly, zgłaszając wszelkie nieprawidłowości w ich strukturze, jak np. niewłaściwe nazwy obiektów, niezgodności typów, brak inicjalizacji instancji obiektów itp. Jest to istotne, ponieważ CLR bywa nadmiernie wyrozumiały dla licznych błędów, które można popełnić generując kod z pominięciem kompilatora. ildasm.exe oraz jego odpowiednik w mono, czyli monodis, pozwalają podejrzeć kod znajdujący się w plikach assembly, co z kolei umożliwia generowanie własnego kodu per analogiam. Wystarczy napisać kod w C#, skompilować, i sprawdzić, jak efekt wygląda w CIL.
Warto też pamiętać o rozszerzeniach mechanizmu refleksji, które powstała w trakcie rozwoju mono. Są to Cecil, pozwalający na inspekcję kodu bez ładowania go do środowiska CLR oraz CodeGeneration, który generuje CIL używając składni opartej na C#.
Na koniec parę ciekawostek odnośnie CLR. Na początek: interfejsy nie istnieją. CIL modeluje je za pomocą klas abstrakcyjnych. To pociąga za sobą (sic!) wielokrotne dziedziczenie. Dalej - nie istnieją też klasy. Są reprezentowane jako powiązane ze sobą struktury danych i funkcje. Każda metoda oczywiście potrzebuje wskaźnika this, który jest przekazywany do niej jako parametr numer 0 (kojarzy się z C++ ?). Pozwala to na takie operacje, jak wywołanie na obiekcie metod, których on nie posiada (poprzez wrzucenie go na stos parametrów metody innego obiektu - na szczęście, jeśli odpowiednie rzutowanie nie byłoby możliwe, wykonanie zakończy się wyjątkiem) lub uruchamianie metod instancji obiektu bez uprzedniego stworzenia go (wywołanie metody, kiedy stos nie zawiera odpowiedniego wskaźnika do obiektu - CLR zachowuje się wtedy, jakby obiekt istniał, ale wszelkie pola ustawione są na wartości domyślne dla swojego typu). Użyteczna dla wielu zastosowań może być możliwość dostępu do metod i pól prywatnych.