niedziela, 11 kwietnia 2010

Reflection w .NET

Reflection w .NET daje nam możliwości otwierania, uruchamiania czy też tworzenia dynamicznie typów podczas działania programu. Gdzie się może nam taka funkcjonalność przydać? Przykładowo w aplikacji do której chcemy mieć możliwość dodawania wtyczek.

Ładowanie Assemblies podczas działania programu.
Aby załadować jakieś assembly można skorzystać z kilku dostępnych metod (przed przystąpieniem do tego dodajemy przestrzeń nazw System.Reflection):
Assembly.Load – ładuje assembly na podstawie nazwy (zwykle z GAC)
Assembly.LoadFile – ładuje assembly z pliku na podstawie podanej ścieżki
Assembly.LoadFrom – ładuje assembly na podstawie nazwy pliku lub ścieżki
Assembly.ReflectionOnlyLoad – w większości przypadków ładuje pakiet z GAC w trybie reflection - only
Assembly.ReflectionOnlyLoadFrom – ładuje assembly w trybie reflection – only z podanej ścieżki

Jeżeli skorzystamy z metody, która zawiera tryb relection – only, nie będziemy mieć możliwości tworzenia obiektów ładowanego pakietu, a jedynie możliwość przeglądania właściwości czy też kodu tych pakietów. Może się to przydać kiedy chcemy tylko zobaczyć jak zostały zdefiniowane funkcje interesującego nas assemby.

Przykład załadowania assembly z pliku. Posłużę się tutaj bardzo dobrze opisanym w Training Kit StringBuilderem.
1. Ładujemy assembly z pliku
2. Za pomocą metody GetType wyodrębniamy interesujący nas typ
3. Za pomocą metody GetConstructor pobieramy konstruktor
4. Używając metody GetMethod wyodrębnimy interesującą nas metodę
5. Za pomocą Invoke() uruchamiamy (jak i również przekazujemy jeśli są potrzebne parametry) metodę
Kod:

            //StringBuilder znajduje się w pliku mscorlib.dll
            Assembly a = Assembly.Load("mscorlib.dll");
            //Pobieramy interesujący nas typ
            Type t = a.GetType("System.Text.StringBuilder");
            //Wyodrębniamy konstruktor
            ConstructorInfo ci = t.GetConstructor(new Type[] { });
            //Invoke wywoła konstruktor - w tym przypadku jest bezparametrowy więc
            //można przesłać albo null albo pustą tablicę
            object sb = ci.Invoke(null);
            //Wyodrębniamy potrzebną nam metodę
            MethodInfo mi = t.GetMethod("Append", new Type[] { typeof(string) });
            //Wywołujemy metodę wraz z parametrem
            mi.Invoke(sb, new object[] { "Ala ma kota" });
            //Wypisujemy zawartość StringBuildera na ekranie
            Console.WriteLine(sb);

Kod można uprościć w tym przypadku. Można od razu podstawić żądany typ:

            Type t = typeof(StringBuilder);

Aby otrzymać listę wszystkich metod, właściwości czy konstruktorów można posłużyć się jedną z metod GetMethods, GetProperties, GetConstructors itd. Dla przykładu wyświetlimy wszystkie właściwości StringBuildera i ich typy:

            PropertyInfo[] propinfo = t.GetProperties();
            foreach (var item in propinfo)
            {
                Console.Write(item.Name);
                Type types = item.PropertyType;
                Console.Write("  " + types.Name);
                Console.WriteLine();
            }

Aby wyświetlić wszystkie metody, właściwości, pola, eventy itp. można skorzystać z poniższego kodu:

            MemberInfo[] allTypes = t.GetMembers();
            foreach (var item in allTypes)
            {
                Console.WriteLine(item.Name);
            }

Za pomocą BindingFlags można dokonać slekcji (same metody, właściwości itp.):

            MemberInfo[] allTypes = t.GetMembers(BindingFlags.GetField | BindingFlags.CreateInstance);

Atrybuty Assembly
Atybuty mogą opisywać różne aspekty pakietu. Dla przykładu może to być wersja, nazwa, rok, opis itp. Dla przykładu wyświetlimy prawa autorskie obecnie wykonywanego assembly:

            Assembly a = Assembly.GetExecutingAssembly();
            foreach (Attribute item in a.GetCustomAttributes(false))
            {
                if (item.GetType() == typeof(AssemblyCopyrightAttribute))
                {
                    Console.WriteLine(((AssemblyCopyrightAttribute)item).Copyright);
                }
            }


Dynamiczne generowanie typów
Korzystając z możliwości Reflection możemy w czasie działania programu stworzyć nasz własny typ dzięki klasą w przestrzeni System.Reflection. Możemy w tym celu skorzystać z klas takich jak: AssemblyBuilder, ConstructorBuilder, EnumBuilder, EventBuilder, FieldBuilder, LocalBuilder, MethodBuilder, ModuleBuilder, ParameterBuilder, PropertyBuilder, TypeBuilder (dostęp do typów otrzymujemy po dodaniu przestrzeni nazw System.Reflection.Emit).

Proces tworzenia dynamicznego typu można przedstawić jako następująca lista czynności:
1. Tworzymy assembly i moduł.
2. W module tworzymy żądany typ.
3. Dla typu definiujemy żądane przez nas elementy jak konstruktowy, właściwości, metody itd.
4. Dla każdej metody tworzymy ILGenerator który pozwala nam zapisać kod dla metody

Utworzenie obiektu i wywołanie metody odbywa się jak to opisano w pierwszej części artykułu.
Kod:

            AssemblyBuilder a = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("test"), AssemblyBuilderAccess.RunAndSave);
            ModuleBuilder mb = a.DefineDynamicModule("module");
            TypeBuilder tb = mb.DefineType("MyClass", TypeAttributes.Class | TypeAttributes.Public);
            ConstructorBuilder cb = tb.DefineDefaultConstructor(MethodAttributes.Public);
            MethodBuilder methb = tb.DefineMethod("SaySomething", MethodAttributes.Public, typeof(void), null);
            ILGenerator genCode = methb.GetILGenerator();
            genCode.EmitWriteLine("Hello");
            genCode.Emit(OpCodes.Ret);


            Type t = tb.CreateType();
            ConstructorInfo ci = t.GetConstructor(new Type[] { });
            object o = ci.Invoke(null);
            MethodInfo mi = t.GetMethod("SaySomething");
            mi.Invoke(o, new object[] { });

Podsumowując dzięki Refleksji jesteśmy w stanie tworzyć w łatwy sposób rozszerzalne aplikacje (wtyczki, pluginy itp.). Oprócz tego jesteśmy także w stanie tworzyć własne typy dynamicznie.

Brak komentarzy:

Prześlij komentarz