Refleksja nad kodem .NET

Platforma .NET zawiera bardzo rozbudowane mechanizmy refleksji, pozwalające "z zewnątrz" odczytać strukturę pakietu .NET. Dodatkowo do niemal każdej składowej można dopisać atrybut, określający np. rolę danego elementu w aplikacji. Wszystko to sprawia, że stosunkowo łatwo pisze się wszelkiego rodzaju systemy, w których dodatkowa funkcja jest dołączana dynamicznie.

Platforma .NET zawiera bardzo rozbudowane mechanizmy refleksji, pozwalające "z zewnątrz" odczytać strukturę pakietu .NET. Dodatkowo do niemal każdej składowej można dopisać atrybut, określający np. rolę danego elementu w aplikacji. Wszystko to sprawia, że stosunkowo łatwo pisze się wszelkiego rodzaju systemy, w których dodatkowa funkcja jest dołączana dynamicznie.

W .NET atrybuty odgrywają bardzo istotną rolę w programowaniu. Służą do oznaczania fragmentów kodu (czy całych pakietów) specjalnymi cechami, które są wykorzystywane w trakcie kompilacji lub linkowania albo w czasie działania programu.

Atrybut może być przypisany do pakietów (assembly), typów (czyli klas, struktur, wyliczeń) oraz poszczególnych metod czy właściwości. Nie może być przypisany np. do pojedynczej instrukcji w ramach metody.

Atrybut stanowi część tzw. metadanych - czyli struktury opisującej zawartość pakietu .NET. Jedną z istotnych nowości w .NET było "samoopisywanie się" pakietu. Dla porównania - w technologii CORBA wymagane są zewnętrzne deskryptory (IDL) opisujące interfejs (to, co komponent udostępnia). W.NET wystarczy "zapytać" sam pakiet, żeby wiedzieć, jakie ma typy, jakie metody mają te typy, jakie są atrybuty itp. Mechanizmem, który to umożliwia, jest refleksja.

Atrybuty mają wiele zastosowań. Chyba najszerzej znanym jest ustawienie właściwości pakietu, które pomagają środowisku .NET wczytać właściwy plik i sprawdzić wersje (czy zbudować tzw. mocną nazwę). Wszystkie te elementy, które są ustawiane z poziomu właściwości projektu:

Konfiguracja ustawień pakietu.

Konfiguracja ustawień pakietu.

przekładają się na odpowiednie wpisy w pliku AssemblyInfo.cs (albo na opcje kompilacji w .NET 2.0):

[assembly: AssemblyTitle("AttrAndReflection")]

[assembly: AssemblyCompany("IDG")]

...

[assembly: AssemblyVersion("1.0.0.0")]

[assembly: AssemblyFileVersion("1.0.0.0")]

Atrybuty służą także do oznaczania metod jako usług WWW i do publikacji informacji w taki sposób, żeby obiekt .NET był traktowany jak komponent COM.

Atrybut Obsolete pozwala wskazać, które elementy klasy będą z kolejnych wersji usuwane - korzysta z tego kompilator i odpowiednie informacje umieszcza na liście błędów i ostrzeżeń. Przy użyciu atrybutów określany jest także zestaw wymaganych uprawnień, potrzebny, aby dana metoda czy klasa mogła działać.

Istotnym zastosowaniem atrybutów jest tzw. serializacja. W .NET można zapisać dowolny obiekt i potem łatwo go odtworzyć. Wystarczy dodać atrybut Serializable. Można też wskazać, które pola mają być nieserializowane (domyślnie zapisywane są wszystkie pola składowe - publiczne i prywatne):

[Serializable]

class SerializableObject {

public string saved = "Saved";

public int[] arr ={ 1, 2, 3 };

private int privateserialized=-1;

[NonSerialized]

public string notsaved = "NotSaved";

}

W tym przykładzie zapisywane są wszystkie pola z wyjątkiem notsaved. Oznaczoną w taki sposób klasę można zapisać w dowolnym strumieniu. Formater jest specjalnym obiektem, który odpowiada za "wpasowanie" struktury w dany format pliku. W .NET są dwa: SoapFormatter, który zapisuje strukturę w standardzie SOAP (jak w usłudze WWW), i BinaryFormatter, gdzie powstaje znacznie mniejszy plik, ale zamiast czytelnego XML dostaniemy strukturę binarną. BinaryFormatter jest znacznie szybszy niż serializacja na SOAP:

SerializableObject obj =

new SerializableObject();

obj.saved = "Changed";

obj.notsaved = "QQQQ";

obj.arr[0] = 9;

Stream stream = File.Open(

@"C:\SerializableObject.xml", FileMode.Create);

SoapFormatter formatter = new SoapFormatter();

formatter.Serialize(stream, obj);

stream.Close();

Należy też dodać referencję do systemowej biblioteki System.Runtime. Serialization.Formatters.Soap.dll, chcąc korzystać z formatera SOAP. Odczyt wykorzystuje funkcję Deserialize. Wystarczy zrzutować wynik na docelowy typ:

obj = null; //Skasowany

stream = File.Open(

@"C:\SerializableObject.xml", FileMode.Open);

formatter = new SoapFormatter();

obj = (SerializableObject)formatter.

Deserialize(stream);

stream.Close();

Jest również inna metoda serializacji obiektów, która daje większą kontrolę nad zapisywaniem poszczególnych pól w formacie XML. Służą do tego atrybuty XmlAttribute i XmlElement. Pierwszy określa nazwę atrybutu XML, w którym zostanie umieszczona wartość danego pola, a drugi - nazwę elementu:

public class SerializableObjectXML

{

[XmlAttribute("MyName")]

public string saved = "Saved";

public string saved1 = "Saved";

[XmlElement("MyName1")]

public string saved2 = "Saved";

}

Załóżmy, że obiekt będzie miał pola zainicjowane w następujący sposób:

SerializableObjectXML objX =

new SerializableObjectXML();

objX.saved = "Changed";

objX.SavedOne = "Changed1";

objX.saved2 = "Changed2";

Serializacja obiektu na XML przypomina to, co było w poprzednim wypadku. Jednak jest kilka istotnych różnic. Po pierwsze, tworzymy XmlSerializer dostosowany do danego typu. Technicznie polega to na tworzeniu w tymczasowym katalogu pliku DLL dostosowanego do danej klasy (i atrybutów opisujących, jak mapować strukturę obiektową na XML). Warto też wspomnieć, że można to przeprowadzić ręcznie (i np. podpisać taki DLL mocną nazwą). W tym celu trzeba użyć narzędzia sgen <NazwaPliku-DllZSerializowanymTypem>. Uwaga! Tak utworzonych plików nie da się użyć po stronie serwera WWW .

Serializacja inaczej

Warto wiedzieć, że oprócz atrybutu można zaimplementować w klasie interfejs ISerializable - trzeba zastosować metodę void GetObjectData(SerializationInfo info, StreamingContext context), która wypełni strukturę SerializationInfo danymi potrzebnymi do późniejszego odzyskania stanu obiektu. Trzeba także utworzyć specjalny konstruktor, który na podstawie kontekstu odzyska obiekt: protected SerializableObject (SerializationInfo info, StreamingContext context)


Zobacz również