Mechanizm refleksji

Jednym z przyczyn dla której platforma .NET jest tak elastyczna jest mechanizm refleksji. Prościej mówiąc modyfikacja programu w czasie jego działania. Przykładem użycia może być wywoływanie metod nieznając ich nazw w czasie pisania programu albo tworzenie obiektów z klas znajdujących się w zewnętrznych bibliotekach (inaczej: system wtyczek).
Żeby nie było nie występuje on tylko w produktach firmy Microsoft, ale także w innych językach: PHP, Java, Perl, Ruby. W których został zaimplementowany już dosyć dawno.

Wracając do tematu zaprezentuję tutaj jak można wykorzystać refleksję w praktyce.

Najpierw o najprostszym przypadku: wywołanie metody jakiegoś obiektu.
W pierwszej kolejności musimy pobrać typ naszego obiektu, możemy pobrać go za pomocą funkcji GetType() jeśli już jakiś obiekt istnieje, albo za pomocą typeof(Klasa). W obu przypadkach zostanie zwrócony obiekt typu Type.
Type jest w stanie dostarczyć nam wiele informacji na temat dowolnego typu, począwszy od konstruktorów, destruktorów, poprzez pola, metody aż po typy po których dziedziczy. Pozwala także na wykonywanie metod i zmianę wartości pól. Do tego ostatniego służy metoda InvokeMember. W najprostszej wersji przyjmuje pięć argumentów:

  1. public Object InvokeMember (
  2. string name,
  3. BindingFlags invokeAttr,
  4. Binder binder,
  5. Object target,
  6. Object[] args
  7. )

Pierwszy name jest nazwą pola, metody do którego chcemy się odwołać.
Drugi określa czego będziemy pod tą nazwą szukali i co zamierzamy z tym zrobić. Najbardziej interesującym flagami są:

  • Public, NonPublic - czy pole jest dostępne ma status publiczny czy prywatny.
  • InvokeMethod - znaleziona metoda zostać wywołana
  • GetField, SetField, GetProperty, SetProperty - ma zostać pobrana/ustawiona wartość pola lub własciwości
  • CreateInstance - zanim cokolwiek się wykona zostanie utworzony nowy obiekt danego typu i na nim będą przeprowadzone operacje.

Na chwilę obecną jeszcze nie za bardzo się orientuję w wykorzystaniu trzeciego parametru binder, więc swobodnie można tam wstawić wartość null. target jest obiektem na którym ma być wykonana operacja. Jeśli wybraliśmy flagę CreateInstance możemy wstawić null. Ostatnim argumentem jest tablica zawierająca wartości potencjalnych argumentów metody lub wartość jaka ma być przypisana polu.
Koniec wstępu teoretycznego, a teraz praktyka:

  1. Type type = GetType();
  2. type.InvokeMember("RotateXCW",
  3. BindingFlags.InvokeMethod |BindingFlags.NonPublic,
  4. null,
  5. this,
  6. null);
  7.  

Prawda, że proste?
Pobieramy typ aktualnego obiektu w którym znajduje się ten fragment kodu, a następnie wywołujemy prywatną (NonPublic) metodę RotateXCW w bieżącym obiekcie.

Jeśli metoda ma argumenty to kod będzie wyglądał następująco:

  1. Type type = GetType();
  2. type.InvokeMember("RotateXCW",
  3. BindingFlags.InvokeMethod |BindingFlags.NonPublic,
  4. null,
  5. this,
  6. new object [] { "Pierwszy argument typu string", 10, new Button() );
  7.  

Przekazujemy tablicę obiektów, w tym przypadku: napis, liczbę (integer) i kontrolkę przycisku.

Skoro już wiadomo w jak prosty sposób można wywołać metodę obiektu no to teraz pokażę, jak stworzyć obiekt i jak zrobić prosty system wtyczek.
Kod do realizujący to zadanie będzie wyglądał mniej więcej tak:

  1. string [] files = Directory.GetFiles(pluginPath, "*.dll");
  2.  
  3. for(int i = 0; i < files.Length; i++)
  4. {
  5. // Ladujemy Assembly z pliku
  6. Assembly asm = Assembly.LoadFrom(files[i]);
  7.  
  8. // Sprawdzamy typy zawarte w bibliotece
  9. foreach (Type type in asm.GetTypes())
  10. {
  11. // Szukamy tylko publicznych, nieabstrakcyjnych klas
  12. if(type.IsPublic && !type.IsAbstract)
  13. {
  14.  
  15. // Sprawdzamy czy dziedziczy po naszym intefrace'ie
  16. Type iFace = type.GetInterface("plugInterfaces.IPlugin", true);
  17.  
  18. // Jezeli tak - zapisujemy do pluginow
  19. if(iFace != null)
  20. {
  21. IPlugin tmp = (IPlugin)Activator.CreateInstance(asm.GetType(type.ToString())); // Tworzymy nową instancję pluginu
  22. plugins.Add(tmp); // Dodajemy plugin
  23.  
  24. tmp = null; // Czyscimy po sobie
  25. }
  26.  
  27. iFace = null; // Czyscimy po sobie
  28.  
  29. }
  30. }
  31. asm = null; // Czyscimy po sobie
  32.  

W pierwszej linii pobieramy listę plików dll z katalogu z wtyczkami. W linii 6 ładujemy każdą dll'kę i następnie pobieramy wszystkie znajdujące się w niej typy (linia 9). Sprawdzamy, czy to jest klasa której instancję możemy bezproblemowo utworzyć (filtrujemy wszystkie klasy abstrakcyjne, interfejsy, itp).
Konstrukcja z linii 16 i 19 służy do zapewnienia, że dana klasa w pełni implementuje intefejs IPlugin (który jest definiowany przez programistę). W 21 tworzymy obiekt oraz wrzucamy go na listę naszych wtyczek. Sprzątamy po sobie i w ostateczności mamy listę wszystkich obiektów-wtyczek implementujących dany interfejs, którą możemy wykorzystać w dowolny już sposób.

3 Responses to “Mechanizm refleksji”

  1. Do tej listy dodać należy jeszcze pythona. Swoją drogą to jakieś guru .Net mówiło kiedyś, że do mechanizmu wtyczek to właśnie IronPython świetnie się nadaje.

  2. Teoretycznie powinno się dopisać jeszcze co najmniej 20 innych języków :)
    Co do wtyczek to według mnie prawie każdy język się do tego świetnie nadaje. Każdy, który pozwala na dynamiczne ładowanie bibliotek. Jedynym problemem może być stopień skomplikowania implementacji tego, aczkolwiek w większości „skomplikowanych” środowisk programista już na samym początku dostaje narzędzia pomocnicze (mam tutaj na myśli Borland C++ Builder, Delphi).

  3. Mechanizm refleksji…

    Dziękujemy za publikację – Trackback z dotnetomaniak.pl…

Leave a Reply