Wybrane elementy C#, BCL Łańcuchy i wyrażenia regularne Zarządzenie pamięcią Operatory i rzutowanie Język obiektowy wywodzący się z C++ i Javy brak bezpośredniego dostępu do pamięci (safe/unsafe code) automatyczne zarządzanie pamięcią, garbage collection brak zmiennych i funkcji globalnych zmienne lokalne nie mogą się przesłaniać bool jest typem ścisłym C# nie wspiera wielodziedziczności wspiera mechanizm refleksji Typy parametryzowane: Generics (polimorfizm parametryczny)
Struktura programu C# // Namespace Declaration using System; namespace MyProgram { // helper class class OutputClass { string myString; // Constructor public OutputClass(string inputString) myString = inputString; } // Instance Method public void printString() Console.WriteLine("{0}", myString); // Program start class class InteractiveWelcome { // Main begins program execution. public static void Main(string[] args) { // Write to console/get input Console.Write("What is your name?: "); Console.Write("Hello, {0}! ", Console.ReadLine()); Console.WriteLine("Welcome to the C# Tutorial!"); // Instance of OutputClass OutputClass outCl = new OutputClass("This is printed by the output class."); // Call Output class' method outCl.printString() } W formacie PascalCase każdy wyraz, z których składa się identyfikator, rozpoczynamy wielką literą np.:WriteLine, ReadLine, Console, SByte. Formatu PascalCase używamy dla nazw typów, stałych nazwanych, właściwości, przestrzeni nazw. W przypadku formatowania camelCase pierwszy wyraz będący częścią identyfikatora piszemy małą literą, pozostałe rozpoczynamy wielką literą. np.: loopCountMax. Formatu camelCase używamy dla nazw zmiennych lokalnych, parametrów funkcji. Program w języku C# rozpoczyna się od metody Main. Program w języku C# kończy się po wykonaniu instrukcji tuż przed nawiasem klamrowym zamykającym blok kodu metody Main. Innym sposobem zakończenia programu jest wywołanie instrukcji return wewnątrz bloku kodu metody Main. “Naming Guidelines” oraz na stronach www: http://home.comcast.net/~lancehunt/CSharp_Coding_Standards.pdf, http://www.idesign.net/idesign/download/Idesign Csharp Coding Standard.zip using System; namespace PierwszyProgram { class NaszaKlasa { static int Dodaj(int zmienna1, int zmienna2) { return zmienna1 + zmienna2; } static void Main(string[] args) { string x = Console.ReadLine(); string y = Console.ReadLine(); int wynik = Dodaj(int.Parse(x), int.Parse(y)); Console.WriteLine(wynik); } } }
Zmienne w C# Typ Zawartość Non-nullable value type Wartość zdefiniowanego typu Nullable value type Wartość zdefiniowanego typu lub null object Null, referencja do obiektu dowolnego typu, referencja do boxowanej wartości dowolnego typu Klasa Null, referencja do instancji klasy, referencja do instancji klasy dziedziczącej Interfejs Null, referencja do instancji klasy implementującej interfejs, referencja do boxowanej wartości typu implementującego interfejs Tablica Null, referencja do instancji tablicy danego lub kompatybilnego typu Delegat Null, referencja do instancji typu delegata
2.2.1 Type System - przykłady Typy wartościowe Typy referencyjne int i = 123; string s = "Hello world"; 123 i s "Hello world" Technika pakowania (Boxing and unboxing) i 123 Przydział pamięci - typy referencyjne przydzielane obszary pamięci na zarządzanej stercie, typy wartościowe są inicjalizowane zerami Zwalnianie pamięci – pamięć na stosie jest zwalniania w momencie, w którym dana zmienna opuszcza bieżący zakres przetwarzania. Zwalnianie pamięci na stercie – odpowiada proces odzyskiwania pamięci. Opakowywanie - utworzenie na zarządzanej stercie kopi zmiennej typu wartości i zwrócenie do tej kopi uchwytu (referencji) Zmienne typu wartości bezpośrednio zawierają dane, dlatego też niektórzy nazywają typ ten typem bezpośrednim. Każda zmienna typu wartości reprezentuje oddzielny obszar pamięci, dlatego operacja na jednej zmiennej nie wpływa na drugą. Zmienne lokalne przechowywane w obszarze pamięci zwanym stosem. Z miejscem przechowywania i sposobem zarządzania tym obszarem związany jest czas życia zmiennej. Czas życia zmiennej możemy zdefiniować jako czas, przez który jest zarezerwowana pamięć dla zmiennej. Pamięć jest zarezerwowana dla zmiennej lokalnej od miejsca jej zadeklarowania do końca bloku, w którym została zadeklarowana. Przed użyciem zmienna musi być zainicjalizowana - musi mieć nadaną wartość. W wersji C# 2.0 dla wszystkich typów wartości wprowadzono pewną formę typu (nullable types), której zmienne mogą przyjmować wartości null (wartość nieokreślona). Typ ten uzyskujemy przez dodanie do końca nazwy typu wartości znaku zapytania ?. int? x; x = null; //OK int y; y = null; //Błąd kompilacji Typy nullable wprowadzono, aby ułatwić współpracę z bazami danych. Skopiowanie wartości zmiennej do zmiennej innego typu nazywamy konwersją. W przypadku konwersji niejawnej nigdy nie ma utraty wartości, może być tylko utracona precyzja - dokładność. W języku C# istnieje jeszcze jedna standardowa konwersja - opakowanie (boxing). Opakowanie wartości polega na niejawnym przekształceniu typu wartości w typ object. Przypisywanie wartości zmiennych Typy wartościowe: Directly contain data, Cannot be null Typy referencyjne: Contain references to objects, May be null modyfikator static – typ/metoda statyczna modyfikator partial class – klasa rozbita na wiele plików klasa może rozbita na wiele plików, np.: public partial class MojaKlasa { .... } Modyfikatory dostępu public – typ lub pole dostępny dla innego kodu w tym samym lub innym assembly private – typ lub pole dostępne tylko dla kodu w tej klasie (domyślne dla pól klasy) protected – typ lub pole dostępne dla kodu w tej klasie lub klasie potomnej internal – typ lub pole dostępne dla kodu tylko w tym assembly (domyślne dla klas) protected internal = protected OR internal int i = 123; object o = i; int j = (int)o; o System.Int32 123 j 123
Typ referencyjny wbudowany: string (System.String) Łańcuchy : łańcuch jest sekwencją wartości typu char porównywane są według wartości niezmienne (ang. immutable) – zmodyfikowane są zapisywane pod innymi adresami niż oryginalne public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable{ public char this[int index] {get;} public int Length {get;} public static int Compare(string strA, string strB); public static int CompareOrdinal(string strA, string strB); public static string Format(string format, object arg0); public int IndexOf(string); public int IndexOfAny(char[] anyOf); public int LastIndexOf(string value); public string PadLeft(int width, char c); public string[] Split(params char[] separator); public string Substring(int startIndex, int length); ... } http://msdn.microsoft.com/en-us/library/system.string(VS.71).aspx System.String typ referencyjny oferujący wartościową semantykę (łańcuchy porównywane są według wartości , a nie miejsca składowania) Łańcuch jest sekwencją wartości typu char. Każde odwołanie do znaku wchodzącego w skład łańcucha jest traktowane jako obiekt typu char. Są niezmienne (immutable) – po utworzeniu niemożna go zmieniać w ramach bieżącej lokalizacji w PAO – nie można go skracać, wydłużać, ani zmieniać zawartych w nim znaków. Zmodyfikowane łańcuchy zawsze są zapisywane w pamięci po innymi adresami nic łańcuchy oryginalne. Klasa System.Text.StringBuilder – po wykonaniu operacji zawartośc obiektu tej klasy jest konwertowana na łańcuch. Powinna być stosowana, gdy budowany łańcuch podlega wielu operacjom konkatenacji i modfikacji. Różna się sposobem obsługi znaków specjalnych Standardowe stałe łańcuchowe - łańcuchy otoczone znakami cudzysłowu (‘’) uwzględniają znaczeni znaków specjalnych, łańcuchy dosłowne – traktują je jak zwykłe znaki // s.PadLeft(10,'.'); ".....Hello" string s = "Hi there."; Console.WriteLine( "{0}", s.ToUpper() ); // Print uppercase copy Console.WriteLine( "{0}", s ); // String is unchanged
Typ referencyjny wbudowany - string (System.String) Metody Opis Compare() porównanie dwóch łańcuchów Concat() utworzenie nowego łańcucha z jednego lub większej liczby Copy() utworzenie nowego łańcucha przez przekopiowanie zawartości Chars[] indekser łańcucha Insert() zwraca nowy łańcuch z dostawionym nowym łańcuchem Remove() usunięcie z łańcucha określonej liczby znaków Split() rozbicie łańcucha na podłańcuchy przy założonym zbiorze ograniczników StartsWith() wskazuje czy łańcuch rozpoczyna się od określonych znaków Substring() wyłuskanie podłańcucha ToLower() zwraca kopię łańcucha składającego się z małych liter Trim() wyrzucenie określonego zbioru znaków z początku i końca łańcucha Methods that appear to modify a String object actually return a new String object that contains the modification { Console.WriteLine("=> String equality:"); string s1 = "Hello!"; string s2 = "Yo!"; Console.WriteLine("s1 = {0}", s1); Console.WriteLine("s2 = {0}", s2); Console.WriteLine(); // Test these strings for equality. Console.WriteLine("s1 == s2: {0}", s1 == s2); Console.WriteLine("s1 == Hello!: {0}", s1 == "Hello!"); Console.WriteLine("s1 == HELLO!: {0}", s1 == "HELLO!"); Console.WriteLine("s1 == hello!: {0}", s1 == "hello!"); Console.WriteLine("s1.Equals(s2): {0}", s1.Equals(s2)); Console.WriteLine("Yo.Equals(s2): {0}", "Yo!".Equals(s2)); } using namespace System.Text; StringBuilder myBuffer = new StringBuilder ("Ala ma kota"); myBuffer.Append(„ a Ola psa."); myBuffer.Insert(11, ','); string Ready = myBuffer.ToString(); // Ala ma kota, a Ola psa. Console.WriteLine("=> String concatenation:"); string s1 = "Programming the "; string s2 = " PsychoDrill "; string s3 = string.Concat(s1, s2); Console.WriteLine(s3); Console.WriteLine();
Stałe łańcuchowe - obsługa znaków specjalnych Łańcuchy standardowe i dosłowne string rst1 = "Hi there!"; string vst1 = @"Hi there!"; string rst2 = "It started, \"Four score and seven...\""; string vst2 = @"It started, ""Four score and seven..."""; string rst3 = "Value 1 \t 5, Val2 \t 10"; // Interprets tab esc sequence string vst3 = @"Value 1 \t 5, Val2 \t 10"; // Does not interpret tab string rst4 = "C:\\Program Files\\Microsoft\\"; string vst4 = @"C:\Program Files\Microsoft\"; string rst5 = " Print \x000A Multiple \u000A Lines"; string vst5 = @" Print Multiple Lines"; Literał jest to część kodu źródłowego, która reprezentuje konkretną wartość danego typu. Stałe znakowe reprezentują pojedynczy znak i zwykle zawierają dany znak umieszczony w apostrofach. char c = 'a', d = '1'; Literał napisowy jest ciągiem znaków ujętym w cudzysłów: string s = "Dowolny napis"; W C# istnieją również literały napisowe dosłowne (verbatim-string-literal). Różnią się od regularnych literałów napisowych tym, że przed cudzysłów otwierający wstawiamy znak małpy @. string s = @"Dowolny napis"; W literałach napisowych dosłownych znak lewy ukośnik nie ma żadnego specjalnego znaczenia. Do deklaracji zmiennej można dodać modyfikator const. Otrzymujemy w ten sposób stałą nazwaną, często nazywaną po prostu stałą. Wartość stałych nazwanych musi być podana w miejscu deklaracji i później jej nie można zmieniać. String Literals Stałe łańcuchowe - obsługa znaków specjalnych string literalOne = "\\\\MySystem\\MyDirectory\\ProgrammingC#.cs"; string literalTwo = "Line One\nLine Two"; Łańcuchy dosłowne: string verbatimLiteralOne = @"\\MySystem\MyDirectory\ProgrammingC#.cs"; string verbatimLiteralTwo = @"Line One Line Two";
StringBuilder Klasa System.Text.StringBuilder konstruowanie i przetwarzanie łańcuchów składowanych w buforze bardziej efektywne w przetwarzaniu łańcuchów public sealed class StringBuilder { public int Capacity {get; set;} public int Length {get; set;} StringBuilder Append(...); StringBuilder AppendFormat(...); StringBuilder Insert(int index, ...); StringBuilder Remove(int startIndex, int length); StringBuilder Replace(char oldChar, char newChar); string ToString(); } StringBuilder is more effective for manipulating strings using System.Text; StringBuilder sb = new StringBuilder("Hi there."); Console.WriteLine("{0}", sb); // Print string sb.Replace("Hi", "Hello"); // Replace a substring Console.WriteLine("{0}", sb); // Print changed string
Typy wyliczeniowe (System.Enum) public abstract class Enum : IComparable, IFormattable, IConvertible Typy zawierające zbiór nazwanych stałych Łatwiejsze czytanie kodu – przypisanie łatwo identyfikowalnych nazw do wartości; Łatwość pisania kodu – IntelliSense podpowiada listę możliwych wartości; Łatwość utrzymania kodu - pozwala zdefiniować zbiór stałych i zadeklarować zmienne, które będą akceptować wartości tylko z tego zbioru enum Color { Red, Green, Blue }; Color a = Color.Red; Color b = Color.Green; Color c = Color.Blue; Console.WriteLine("Wartości typu Color: "); foreach(string s in Enum.GetNames(typeof(Color))) Console.WriteLine(s); Console.WriteLine("Blue jest wartościa typu Color ?: {0}", Enum.IsDefined(typeof(Color),"Blue")); Console.WriteLine("Yellow jest wartościa typu Color ?: {0}", Enum.IsDefined(typeof(Color), "Yellow")); funkcja GetNames z klasy System.Enum Definiuje się go przy pomocy słowa kluczowego enum, po którym występuje nazwa nowego typu, a następnie w nawiasach klamrowych podajemy nazwy symboliczne dopuszczalnych wartości, oddzielone przecinkami. Można również przypisać w sposób jawny wartości poszczególnym elementom np.: enum Kolory{czerwony=4, zielony, niebieski=7}; Element zielony ma w sposób niejawny przypisaną wartość 5. Uwaga: Typy wyliczeniowe, jak inne typy definiowane przez użytkownika (klasy, struktury), nie można definiować wewnątrz bloku metody. Przy definicji typu wyliczeniowego można podać, jaki typ całkowity jest typem bazowym dla definiowanego typu wyliczeniowego. Typem bazowym może być dowolny tym całkowity poza typem char. enum PoryRoku : byte {Wiosna, Lato, Jesien, Zima}; http://msdn.microsoft.com/en-us/library/system.enum(VS.71).aspx enum Maly : byte { mini , mikro } Odwołanie Color a = Color.Red Color b = Color.Green Color c = Color.Blue
Tablice Struktury danych Pojedynczy element dostępny za pomocą indeksu jednorodność - jest złożona z elementów tego samego typu zwanego typem podstawowym tablicy swobodny dostęp - wszystkie elementy tablicy są jednakowo dostępne - w dowolnej kolejności i w jednakowym czasie stała (niezmienna) ilość elementów ciągłość - zajmuje ciągły obszar pamięci Pojedynczy element dostępny za pomocą indeksu Deklaracji zmiennej tablicowej typ [] nazwa; Utworzenie tablicy - obiektu tablicowego typ [] nazwa = new typ[ilosc_Elementow]; lub nazwa = new typ[liczba_Elementow];
Operacje na tablicach Metoda Sort klasy Array Metody Copy, Clone, CopyTo klasy Array int[] tab1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int[] tab2 = {11,12,13,14,15,16,17,18,19 }; Array.Copy(tab1,2,tab2,4,4); foreach (int i in tab2) { Console.Write("{0}, ",i); } 11, 12, 13, 14, 3, 4, 5, 6, 19, int[] tab1 = { 4, 1, 83, 41, 53, 36, 47, 18}; Array.Sort(tab1); foreach (int i in tab1) { Console.Write("{0}, ", i); } Metoda Sort klasy Array Ustawianie wartości elementów tablicy (po utworzeniu tablicy). Kopiowanie wartości elementów jednej tablicy do drugiej tablicy. Odwracanie tablicy. Ustawianie elementów tablicy w odpowiedniej kolejności - sortowanie. Sprawdzanie czy tablica zawiera element o danej wartości. Skopiowanie wartości jednej zmiennej tablicowej do drugiej zmiennej tablicowej, powoduje że obie zmienne tablicowe będą odwoływać się do tego samego obiektu tablicowego. kopiowania tablic z określeniem liczby elementów, z podaniem numeru indeksu elementu tablicy źródłowej, który należy skopiować pierwszy oraz podaniem indeksu elementu tablicy docelowej, od którego mają być nadpisane elementy W metodzie Reverse w nawiasach okrągłych podajemy tablicę, która ma zostać odwrócona, następnie po przecinku indeks elementu, od którego Podobnie jak w przypadku sortowania, można przeszukiwać tylko tablice jednowymiarowe. 1, 4, 18, 36, 41, 47, 53, 83, Metoda Reverse klasy Array Exists, FindLast, FindAll, FindIndex, FindLastIndex, …
Typy referencyjne użytkownika - klasy [modyfikator_dostępu] class NazwaKlasy { // ... } Składowe klasy Pola lub zmienne składowe Metody składowe (funkcjonalność) [modyfikator_dostępu] typ_pola nazwa_pola [= wyrazenie]; [modyfikator_dostępu] typ_zwracany nazwa_metody(argumenty) { ciało_metody }
Korzystanie z obiektu Ukrywanie informacji – modyfikatory dostępu Tworzymy przy pomocy operatora new Odwołanie się do składowych Klasa1 zmienna1; zmienna1 = new Klasa1(); lub Klasa1 zmienna1 = new Klasa1(); zmienna1.X = 20; //odwołanie się do pola zmienna1.Metoda1(); //wywołanie metody Ukrywanie informacji – modyfikatory dostępu private Dostępne tylko z metod danej klasy public Ogólnie dostępne protected dostępne dla klas i potomków klasy internal dostęp dla klas z pakietu internal protected dostęp dla klas z pakietu lub potomnych W C# wszystkie metody oraz zmienne muszą być składowymi klas lub struktur. Wrotęc skrótu może pełnic role identyikatora np. przy umieszczaniu obiektu w kolekcji tablicy haszującej Metody klasy object ToString - domyślna wersja tej metody zwraca w pełni kwalifikowaną nazwę klasy Equals - domyślnie porównuje referencje GetHashCode – wartość skrótu dla obiektu
Konstruktor Metoda klasy Metoda wywoływana tuż po utworzeniu obiektu Posiada taką samą nazwę jak klasa w której jest zdefiniowany Nie określamy wartości zwracanej Wywoływany przez operator new Można go przeciążyć class NazwaKlasy{ public NazwaKlasy(){ //konstruktor bezargumentowy ... } public NazwaKlasy(int arg1,double arg2){ //konstruktor z argumentami ... } } NazwaKlasy x1 = new NazwaKlasy(); NazwaKlasy x1 = new NazwaKlasy(2, 3.3);
Klasy częściowe - wieloplikowe //Plik1.cs partial class Klasa1 { //częściowa definicja klasy } //Plik2.cs partial class Klasa1 { //częściowa definicja klasy } Jeden plik z definicją klasy jest do naszej dyspozycji i możemy dodawać tam własny kod, drugi jest uaktualniany przez środowisko Visual Studio i nie powinniśmy go modyfikować. Obie części będą tworzyły jedną klasę pod warunkiem, że będą miały tę samą, w pełni kwalifikowaną nazwę, czyli będą znajdować się w tej samej przestrzeni nazw. Wszystkie części muszą być dostępne w czasie kompilacji – nie możemy w ten sposób rozszerzac klas już skompilowanych. Wszystkie części muszą być dostępne w czasie kompilacji Klasy częściowe używane są w środowisku Visual Studio przez generatory kodu; mogą być również przydatne w przypadku pracy grupowej
Sposoby przesyłania argumentów do metody Przesyłanie przez wartość Przesyłanie przez referencję Przesyłanie jako parametr wyjściowy static void Nazwa(int x){...} int a = 5; Nazwa(a); static void Nazwa(ref int x){...} int a = 5; Nazwa(ref a); W języku C# mamy trzy sposoby przesyłania argumentów do metody: przez wartość, przez referencję, jako parametr wyjściowy . Przesyłanie przez wartość jest domyślnym mechanizmem przesyłania argumentów do metody. Argument jest traktowany jako parametr wejściowy. Przesyłanie przez wartość definiujemy przez podanie typu argumentu i jego nazwy, czyli tak jak to było pokazane wcześniej w tym rozdziale. W miejscu wywołania metody, każdy parametr formalny przesyłany przez wartość reprezentuje nowo utworzoną zmienną lokalną. Zmienne te inicjalizowane są wartościami odpowiednich parametrów aktualnych. Argument przesyłany przez referencję traktowany jest, zarówno jako parametr wejściowy jak i parametr wyjściowy. Przesyłanie przez referencję definiujemy przez dodanie przed nazwą typu parametru słowa ref, dla każdego argumentu przesyłanego przez referencję. static void PrzezReferencje(ref int x,ref int y){...} W miejscu wywołania metody, dla parametrów przesyłanych przez referencję, nie jest tworzona nowa zmienna. Parametr formalny reprezentuje ten sam obszar pamięci, tą samą zmienną, co parametr aktualny. W celu wywołania metody, przed każdym argumentem przesyłanym przez referencję musimy postawić słowa ref. PrzezReferencje(ref akt1,ref akt2); Przy powyższym wywołaniu, zmienna akt1 wewnątrz metody PrzezReferencje będzie się nazywać ("będziemy przezywać") x, a zmienna akt2 będzie się nazywać y. Zmiana wartości parametru formalnego wpływa na wartość parametru aktualnego. Argument przesyłany jako parametr wyjściowy traktowany jest jako parametr wyjściowy. Przesyłanie argumentu jako parametr wyjściowy definiujemy przez dodanie przed nazwą typu parametru słowa out, dla każdego argumentu przesyłanego jako parametr wyjściowy. static void JakoWyjsciowy(out int x,out int y){...} W miejscu wywołania metody, dla parametrów przesyłanych jako parametr wyjściowy, podobnie jak w przypadku parametrów przesyłanych przez referencję, nie jest tworzona nowa zmienna. Parametr formalny reprezentuje ten sam obszar pamięci, tą samą zmienną, co parametr aktualny. W celu wywołania metody, przed każdym argumentem przesyłanym jako parametr wyjściowy musimy postawić słowa out. JakoWyjsciowy(out akt1,out akt2); Przy powyższym wywołaniu, zmienna akt1 wewnątrz metody JakoWyjsciowy będzie się nazywać ("będziemy przezywać") x, a zmienna akt2 będzie się nazywać y. Zmienna przesyłana jako parametr wyjściowy do metody, nie musi być wcześniej zainicjalizowana. Wewnątrz metody parametry przesyłane przez referencję traktowane są jako zmienne niezainicjalizowane. Przed wyjściem z metody każdy parametr przesyłany jako parametr wyjściowy musi mieć nadaną wartość. static void Nazwa(out int x){...} int a; Nazwa(out a);
Właściwości (Properties) Definiuje „wirtualny” atrybut get - właściwość do odczytu set - właściwość do zapisu argument o nazwie value class Student { ... private int numerIndeksu; //definicja właściwości public int NumerIndeksu { get { return numerIndeksu; } set { numerIndeksu = value; } } } [modyfikator dostępu] typ Nazwa { get{...} set{...} } Student s = new Student(); s.NumerIndeksu = 1234; int nr = s.NumerIndeksu; Console.WriteLine(s.NumerIndeksu); Pojęcie reprezentuje pewien atrybut klasy, ale w celu jego implementacji trzeba wywołać pewne funkcje Jedną wartość można w prosty sposób obliczyć na podstawie drugiej Dostęp do pola klasy musi być kontrolowany w celu zapewnienia spójności (poprawności) obiektu Ukrycie implementacji (typu) Funkcje set i get mogą być virtualne abstrakcyjne (czysto virtualne) metodami statycznymi o różnym stopniu dostępności
Indekser Zdefiniowanie property dla this public class Skyscraper { Story[] stories; public Story this [int index] { get { return stories[index]; } set { if (value!=null) { stories[index] = value; } SkyScraper searsTower = new SkyScraper(); searsTower[155] = new Story(“Observation Deck”); searsTower[0] = new Story(“Entrance”); Indeksery – właściwości sparametryzowane definiowane są wewnątrz klas a ich ciała mogą zawierać akcesory i mutatory 9get, set). Indekser może otrzymywać na wejściu jeden lub wiele parametrów, a nazwa indeksera jest reprezentowana prze słowo kluczowe this. Oferuje sposób dostępu do kolekcji wartości utrzymywanych w ramach pojedynczego obiektu klasy –parametry jak indeks kolekcji. Give array like behaviour to any class
Struktury – struct są zawsze wartościami (na stosie, kopiowanie) mogą zawierać pola, interfejsy, funkcje składowe i konstruktory z argumentami można definiować struktury częściowe i definiować wewnątrz nich metody częściowe są zawsze zapieczętowane możemy sterować dostępem do składowych za pomocą modyfikatorów dostępu Modyfikator Opis public dostępne zewsząd (domyślny) private tylko wewnątrz struktury / klasy protected dla klas dziedziczących internal tylko w pakiecie protected internal dla klas dziedziczących w pakiecie Nie jest w pełni typem obiektowym, ale może zawierać pola oraz metody możemy sterować dostępem do składowych przy pomocy słów kluczowych public i private metodzie zdefiniowanej wewnątrz struktury możemy używać słowa kluczowego this można definiować struktury częściowe i definiować wewnątrz nich metody częściowe
Struktury - przykład os Zarządzana sterta Definicja typu strukturalnego Użycie typu strukturalnego struct Osoba{ public string Imie,Nazwisko; public int RokUrodzenia; } Osoba os; os.Imie = "Jan"; os.Nazwisko = "Kowalski"; os.RokUrodzenia = 1988; ... Console.Write("Pan(i) {0} {1}", os.Imie, os.Nazwisko); os os.Nazwisko . 1988. Kowalski os.Imie Jan os.RokUrodzenia Zarządzana sterta
Wyjątki try { // Some code that could throw an exception. } catch (TypeAException e) // Code to do any processing needed. // Rethrow the exception throw; catch(TypeBException e) // Wrap the current exception in a more relevant // outer exception and rethrow the new exception. throw(new TypeCException(strMessage, e)); finally // Code that gets executed regardless of whether // an exception was thrown. Niedozwolone jest umieszczanie bloku catch z typem wyjątku ogólnym, przed blokiem catch z typem wyjątku bardziej szczegółowym, ponieważ kod obsługi wyjątku szczegółowego nigdy nie zostałby wykonany. Często się zdarza, że chcemy wykonać pewne instrukcje bez względu na to, czy nie było zgłoszonego wyjątku, czy wyjątek był zgłoszony i został obsłużony lub czy został zgłoszony i nieprzechwycony. Instrukcje, które muszą być zawsze wykonane na zakończenie bloku try umieszczamy wewnątrz bloku finally. Blok finally może wystąpić tuż za blokiem try, gdy z blokiem try nie ma związanego żadnego bloku catch albo po wszystkich blokach catch skojarzonych z danym blokiem try. Wyjątek możemy zgłosić samodzielnie. Służy do tego instrukcja throw: throw new TypWyjatku(); Z wyjątkiem możemy skojarzyć również pewien komunikat, informujący o źródłach błędu. Dokonujemy to w następujący sposób: throw new TypWyjatku("Treść komunikatu");
Typy wyjątków Exception ArgumentNullException SystemException ArgumentOutOfRangeException ArgumentException ArithmeticException DivideByZeroException FormatException OverflowException IndexOutOfRangeException NullReferenceException W pewnym miejscu programu wykrywamy błąd, pewną niezgodność z oczekiwaniami. Nie wiemy jednak jak wykryty błąd poprawić w miejscu jego znalezienia. Przerywamy więc wykonywanie programu i szukamy miejsca w programie, gdzie jest podane jak z danym błędem sobie poradzić. Klasa Exception jest najbardziej ogólną klasą wyjątku i może reprezentować wszystkie wyjątki. Klasa ApplicationException reprezentuje wszystkie wyjątki specyficzne dla danej aplikacji. Definicja specyficznych wyjątków, czyli własnych, jest poza zakresem tego kursu. Klasa SystemException jest klasą ogólną dla pewnej części predefiniowanych wyjątków np.: ArithmeticException - jest zgłaszany w razie wystąpienia błędu podczas operacji arytmetycznych lub konwersji. Może być spowodowany np.: przez dzielenie przez zero w dzieleniu liczb całkowitych (DivideByZeroException), w przypadku wystąpienia nadmiaru (OverflowException) - patrz temat konwersje i operator checked w poprzednim rozdziale. FormatException - jest zgłaszany np. przez metody klasy Convert, gdy podany napis nie reprezentuje wartości, do której chcemy go przekonwertować. NullReferenceException - jest zgłaszany, kiedy używana jest zmienna referencyjna, która ma wartość null. OutOfMemoryException - jest zgłaszany, kiedy występuje niewystarczająca ilość pamięci, np. gdy próbujemy utworzyć niwy obiekt przy pomocy operatora new. IndexOutOfRangeException - jest zgłaszany, gdy następuje odwołanie do nieistniejącego elementu tablicy. Tablice są opisane w rozdziale szóstym tego kursu. ArgumentException - jest zgłaszany w przypadku, kiedy jeden z argumentów dostarczanych do metody jest nieprawidłowy Może być to spowodowane np. przesłaniem wartości null zamiast odwołania do konkretnego obiektu (ArgumentNullException) lub przesłaniem wartości spoza dopuszczalnego zakresu (ArgumentOutOfRangeException). Więcej na temat przesyłania argumentów do metody można znaleźć w rozdziale dziewiątym tego kursu. OutOfMemoryException ApplicationException
Obsługa wyjątków - użycie bloków try i catch using System; class ExceptionTestClass { public static void Main() int x = 0; try int y = 100/x; } catch (ArithmeticException e) Console.WriteLine("ArithmeticException Handler: {0}", e.ToString()); catch (Exception e) Console.WriteLine("Generic Exception Handler: {0}", e.ToString()); /* This code example produces the following results: ArithmeticException Handler: System.DivideByZeroException: Attempted to divide by zero. at ExceptionTestClass.Main() */ Wyjątki klas bardziej szczegółowych powinny być przechwytywane wcześniej W przypadku, gdy pewien fragment kodu może powodować zgłoszenie wyjątku, a my chcemy go obsłużyć, czyli wiemy jak z danym błędem sobie poradzić, umieszczamy dany fragment kodu w bloku try. Możemy powiedzieć, że kod jest pilnowany lub próbowany na wystąpienie ewentualnych błędów. Z każdym blokiem try musi być związany, co najmniej jeden blok catch lub finally. W po słowie catch w nawiasach okrągłych podajemy typ wyjątku, który chcemy obsłużyć. W bloku kodu instrukcji catch podajemy kod obsługi wyjątku. try { //kod "pilnowany" } catch(TypWyjatku ex) { //kod obsługi wyjątku } Identyfikator ex jest uchwytem do zgłoszonego obiektu wyjątku. Jeżeli podczas wykonywania kodu pilnowanego nie zostanie zgłoszony żaden wyjątek, to instrukcje bloku catch (kod obsługi wyjątku) zostają pominięte. W przypadku, gdy podczas wykonywania kodu pilnowanego zostanie zgłoszony wyjątek typu takiego samego, jak który jest podany w instrukcji catch lub typu, dla którego typ podany w instrukcji catch jest typem ogólnym (bazowym), wtedy wykonanie kodu pilnowanego zostaje przerwane i przechodzimy do wykonywania kodu obsługi wyjątku. Pamięć zarezerwowana dla wszystkich zmiennych zadeklarowanych w bloku try zostaje zwolniona, czyli zmienne lokalne zadeklarowane w bloku try zostają zniszczone. Po wykonaniu wszystkich instrukcji kodu obsługi wyjątku program przechodzi do wykonania instrukcji znajdujących się za blokiem catch, oczywiście pod warunkiem, że w kodzie obsługi wyjątku nie został zgłoszony żaden wyjątek. W przypadku, gdy typ zgłoszonego wyjątku jest niezgodny z typem wyjątku podanym w instrukcji catch, następuje próba znalezienia zewnętrznego bloku try, w którym omawiany wcześniej bloki try oraz catch są zawarte. Jeżeli taki zewnętrzny blok zostanie znaleziony, sprawdzane jest, czy typ podany w instrukcji catch związanej z zewnętrznym blokiem try, jest zgodny z typem zgłoszonego wyjątku. Jeżeli typ jest zgodny, program przechodzi do wykonania kodu obsługi wyjątku (oczywiście tego zewnętrznego), w przeciwnym razie szukamy kolejnego zewnętrznego bloku try. Poszukiwania zewnętrznego bloku try trwają aż do napotkania końca metody Main. Jeżeli nie zostanie znaleziony blok try, z którym skojarzona jest instrukcja catch z odpowiednim typem wyjątku, program zostaje przerwany i zostaje wyświetlony odpowiedni komunikat o kłopotach z danym programem.
Interfejs Zbiór funkcji pod wspólną nazwą Słowo kluczowe interface Same deklaracje - brak implementacji Składowe interfejsu mogą być metodami, właściwościami, zdarzeniami lub indekserami Wszystkie składowe publiczne (przez domyślność) Interfejs może dziedziczyć po wielu interfejsach Klasa (interfejs) może implementować wiele interfejsów Klasa musi implementować wszystkie metody swoich bazowych interfejsów interface IControl { void Paint(); } interface ITextBox: IControl void SetText(string text); interface IListBox: IControl void SetItems(string[] items); interface IComboBox: ITextBox, IListBox {} interface IDataBound { void Bind(Binder b); } public class EditBox: IControl, IDataBound { public void Paint() {...} public void Bind(Binder b) {...} } Brak pól
Interfejs - przykład EditBox editBox = new EditBox(); IControl control = editBox; IDataBound dataBound = editBox; object obj = new EditBox(); IControl control = (IControl)obj; IDataBound dataBound = (IDataBound)obj; Klasę implementującą jeden lub wiele interfejsów można traktować jako klasę należącą do wielu typów. Może być postrzegana jak instancja typu każdego z implementowanych interfejsów. Możliwość traktowania odmiennych klas w podobny sposób, jeśli implementują ten sam interfejs class Klasa : INazwa { String s; public virtual void f(){...} public virtual String Wlasciwosc { get {return s;} set {s = value;} } public virtual event EventHandler zdarzenie; //Słowo virtual jest opcjonalne interface INazwa{ void f(); String Wlasciwosc{ get; set; } event EventHandler zdarzenie; //int i; - błąd When a class or struct implements a particular interface, instances of that class or struct can be implicitly converted to that interface type. For example EditBox editBox = new EditBox(); IControl control = editBox; IDataBound dataBound = editBox; In cases where an instance is not statically known to implement a particular interface, dynamic type casts can be used. For example, the following statements use dynamic type casts to obtain an object’s IControl and IDataBound interface implementations. Because the actual type of the object is EditBox, the casts succeed. object obj = new EditBox(); IControl control = (IControl)obj; IDataBound dataBound = (IDataBound)obj;
Jawna implementacja interfejsu interface Interfejs1 { void f(); } interface Interfejs2 { void f(); } class Klasa : Interfejs1, Interfejs2 { public void f() { Console.WriteLine( "Implementacja w sposób niejawny"); } void Interfejs2.f() { Console.WriteLine( "Implementacja w sposób jawny"); } }
Interfejsy standardowe - System.Collections ICollection Interfejs bazowy dla wszystkich klas kolekcji. IComparer Metody do porównywania dwóch obiektów IDictionary Reprezentacja danych w formie kolekcji par klucz-wartość IEnumerator, IEnumerable Iteracyjne przeszukiwanie elementów kolekcji IHashCodeProvider Generowanie skrótów dla obiektów IList Operacje na listach … Zawiera m.i. właściwości: liczba elementów w kolekcji, dane o bezpieczeństwie wielowątkowym; metodę kopiująca elementy kolekcji do tablicy. IDictionaryEnumerator Iteracyjne przeszukiwanie zawartości obiektów implementujących IDictionary IClonable IComparable
Diagram interfesjów System.Collections // implementacja IEnumerable public IEnumerator GetEnumerator ( ) { return ( IEnumerator ) this ; } // implementacja IEnumerator public void Reset ( ) { .... } public bool MoveNext ( ) { ... } public object Current ( ) { get { .... } } Interface ICollection, Interface ICollection<T> Bazowy interfejs dla kolekcji public interface ICollection<T> : IEnumerable<T>, IEnumerable public interface ICollection : IEnumerable { // Properties int Count {get;} // number of elements bool IsSynchronized {get;} // collection synchronised? object SyncRoot {get;} // returns object for synchronisation void CopyTo(Array a, int index); // copies the elements into array (starting at position index) …
Interfejsy ogólne: IEnumerable and IEnumerator Wszystkie kolekcje po których można iterować implementuja IEnumerable IEnumerator realizuje funkcjonalność Iteratora interface IEnumerable { IEnumerator GetEnumerator(); } IEnumerable<T> IEnumerator<T> GetEnumerator() IEnumerator<T> T Current { get; } interface IEnumerator { object Current {get;} bool MoveNext(); void Reset(); }
IComparable and IComparer - sortowanie Porównanie obiektów IComparable jest interfejsem dla typów posiadajacych porządek public interface IComparable { int CompareTo(object obj); // <0 if this < obj, 0 if this == obj, >0 if this > obj } public interface IComparable<T> { int CompareTo(T obj); // <0 if this < obj, 0 if this == obj, >0 if this > obj IComparer jest interfejsem realizującym porównywanie obiektów public interface IComparer { int Compare(object x, object y); // <0 if x < y, 0 if x == y, >0 if x > y } public interface IComparer<T> { int Compare(T x, T y); // <0 if x < y, 0 if x == y, >0 if x > y classes implementing IComparable are values types like Int32, Double, DateTime, … class Enum as base class of all enumeration types class String IComparable<T> int CompareTo(T other) IComparer<T> int Compare( T x, T y )
Klasy kolekcji - System.Collections IEnumerable IEnumerator GetEnumerator() IEnumerator Object Current bool MoveNext() void Reset() ICollection int Count void CopyTo(Array a, int pos) IList object this[int i] void Clear() int Add(object x) void Insert(int pos, object x) void Remove(object x) void RemoveAt(int pos) bool Contains(object x) int IndexOf(object x) IDictionary ICollection Keys ICollection Values object this[object key] void Clear() void Add(object key, object value) void Remove(object key) bool Contains(object key) Stack Queue BitArray Types for dealing with sets, lists and dictionaries. No special type for sets Array ArrayList ListDictionary Hashtable SortedList
System.Collections - przykład ArrayList using System; using System.Collections; namespace Kolekcje { class Element { public int Liczba; public Element(int liczba) this.Liczba = liczba; } } class Program { static void Main(string[] args) { Element element_1 = new Element(10); Element element_2 = new Element(20); ArrayList lista = new ArrayList(); lista.Add(element_1); lista.Add(element_2); Console.Write("Elementy: "); foreach (Element element in lista) Console.Write(element.Liczba + " "); } Console.ReadKey();
Typy ogólne, uniwersalne (Generics) Typy uniwersalne mogą być stosowane do implementacji klas , struktur, interfejsów i metod. Obsługują wiele typów danych. Parametry typów są umieszczone w nawiasach ostrych <> w deklaracji uniwersalnego typu. class Klasa { public static void Swap<T>(ref T a, ref T b) { T tmp = a; a = b; b = tmp; } public static T Max<T>(T a, T b) where T: IComparable { if(a.CompareTo(b) > 0) return a; return b; klasy umożliwiające deklarowanie jakiego typu elementy mają znajdować się w danej kolekcji . Główną zaletą jest przyspieszenie działania kolekcji. Podczas dodawania elementu nie jest on rzutowany na typ „object” (chyba że tego chcemy), a podczas pobierania elementu z kolekcji nie musimy z powrotem rzutować go na odpowiedni typ. Następną zaletą korzystania z typów generycznych jest unikanie wielu błędów już na etapie kompilacji. Generics are classes, structures, interfaces, and methods that have placeholders (type parameters) for one or more of the types that they store or use. A generic collection class might use a type parameter as a placeholder for the type of objects that it stores; the type parameters appear as the types of its fields and the parameter types of its methods. A generic method might use its type parameter as the type of its return value or as the type of one of its formal parameters.
System.Collections.Generic System.Collections.Generic dostarcza klas uniwersalnych dla kolekcji. IEnumerable IEnumerator IEnumerable<T> IEnumerator<T> GetEnumerator() IEnumerator<T> T Current IComparable<T> int CompareTo(T obj )) IComparable ICollection<T> int Count void Clear() bool Contains(T item) void Add(T item) void Remove(T item) void CopyTo(Array a, int pos) Stack<T> Queue<T> IComparer Namespace System.Collections.Generic provides generic classes for collections IList<T> T this[int i] void Insert(int pos, T x) void RemoveAt(int pos) int IndexOf(T x) LinkedList<T> IComparer<T> int Compare(T x, T y) Comparer<T> List<T>
System.Collections.Generic - przykład using System; using System.Collections.Generic; namespace KolekcjeGeneryczne { class Program static void Main(string[] args) List<int> lista = new List<int>(); lista.Add(2); lista.Add(5); int suma = lista[0] + lista[1]; Console.WriteLine("Suma wynosi: " + suma); Console.ReadKey(); }
Strumienie System.IO Podstawowe operacje na strumieniach: (Stream) long Length long Position int Read(byte[] buf, int pos, int len) int ReadByte() Write(byte[] buf, int pos, int len) WriteByte(byte x) long Seek(long pos, SeekOrigin org) SetLength(long len) Flush() Close() FileStream string Name Lock(int pos, int len) Unlock(int pos, int len) MemoryStream int Capacity byte[] GetBuffer() WriteTo(Stream s) NetworkStream bool DataAvailable BufferedStream CryptoStream Decorators Strumienie w bibliotece .Net Framework W celu skorzystania z pliku, musimy skojarzyć z nim pewien obiekt zwany strumienie. Strumień jest to pewna warstwa abstrakcyjna, która umożliwia nam zapis i odczyt danych, nie tylko z pliku, ale z różnych źródeł. Na strumieniu mamy zdefiniowane pewne operacje, chociaż ich dostępność zależy od typu strumienia i sposobu jego otwarcia. Grupa pierwsza udostępnia najprostszy interfejs, w którym do strumienia można zapisywać oraz z niego pobierać tylko pojedyncze bajty. Grupa druga pozwala na dodanie szyfrowania lub buforowania strumienia. Przy pomocy obiektów klas grupy trzeciej możemy wymieniać ze strumieniem informacje w formie napisów, liczb i innych obiektów Podstawowe operacje na strumieniach: czytanie danych (reading) - pobieranie danych ze strumienia i umieszczanie ich w pewnej strukturze danych zapis danych (writting) - wstawienie danych do strumienia z pewnej struktury danych ustawienie bieżącej pozycji w strumieniu (seeking)
Klasy używane do odczytu/zapisu z/do strumienia … TextReader StreamReader StringReader TextWriter StreamWriter StringWriter BinaryReader BinaryWriter Stream Klasy używane do odczytu/zapisu z/do strumienia System.IO.BinaryReader, System.IO.BinaryWriter System.IO.StreamReader, System.IO.StreamWriter FileStream MemoryStream NetworkStream
Klasa Stream Bazowe właściwości strumienia public abstract class Stream : MarshalByRefObject, IDisposable { public abstract bool CanRead { get; } public abstract bool CanSeek { get; } public abstract bool CanWrite { get; } public abstract int Read(out byte[] buff, int offset, int count); public abstract void Write(byte[] buff, int offset, int count); public virtual int ReadByte(); public virtual void WriteByte(byte value); public virtual IAsyncResult BeginRead(…); public virtual IAsyncResult BeginWrite(…); public virtual int EndRead(…); public virtual int EndWrite(…); public abstract long Length { get; } public abstract long Position { get; set; } public abstract long Seek(long offset, SeekOrigin origin); public abstract void Flush(); public virtual void Close(); ... } Bazowe właściwości strumienia Synchroniczny odczyt i zapis Asynchroniczny odczyt i zapis Długość i aktualna pozycja Pozycjonowanie Wysłanie danych i zamknięcie strumienia
Output of standard types in binary format Writer (TextWriter) string NewLine Write(bool b) Write(char c) Write(int i) ... Write(string format, params object[] arg) WriteLine() WriteLine(bool b) WriteLine(char c) WriteLine(int i) WriteLine(string format, params object[] arg) Flush() Close() StreamWriter Stream BaseStream StreamWriter(Stream s) StringWriter StringWriter() string ToString() formatted text output BinaryWriter Stream BaseStream BinaryWriter(Stream s) BinaryWriter(Stream s, Encoding e) Write(bool b) Write(char c) Write(int i) ... long Seek(int pos, SeekOrigin org) Flush() Close() Output of standard types in binary format using System; using System.IO; class Test { // writes command line arguments to a file static void Main(string[] arg) { FileStream s = new FileStream("xxx.txt", FileMode.Create); StreamWriter w = new StreamWriter(s); for (int i = 0; i < arg.Length; i++) w.WriteLine("arg[{0}] = {1}", i, arg[i]); w.Close(); }
Operacje na strukturze systemu plików Directory służy do bezpośrednich operacji na plikach i katalogach File udostępnia metody do operacji na plikach Path operacje na tekście zawierającym informacje o ścieżce dostępu do pliku lub katalogu FileSystemWatcher ustawienie kontroli na pliku lub katalogu W bibliotece .Net Framework istnieją również klasy, które umożliwiają nam operacje na strukturze systemu plików np. sprawdzenie czy istnieje żądany plik, sprawdzenie wielkości pliku, wylistowanie zawartości katalogu, utworzenie katalogu itp. Directory - służy do bezpośrednich operacji na plikach i katalogach File - udostępnia metody do operacji na plikach Path - zawiera operacje na tekście zawierającym informacje o ścieżce dostępu do pliku lub katalogu FileSystemWatcher - ustawienie kontroli na pliku lub katalogu Uwaga: W przypadku gdy będziemy wykonywać kilka operacji na pojedynczym pliku lub katalogu zamiast korzystać z klas File oraz Directory, powinniśmy skorzystać z obiektów klas FileInfo oraz DirectoryInfo.
Klasa Directory public sealed class Directory { public static DirectoryInfo CreateDirectory(string path); // creates directories and subdirectories public static void Move(string src, string dest); // moves directory src to dest public static void Delete(string path); // deletes an empty directory public static void Delete(string path, bool recursive); // deletes directory with contents public static bool Exists(string path); // checks if directory exists public static string[] GetFiles(string path); // returns all file names in path public static string[] GetFiles(string path, string searchPattern); public static string[] GetDirectories(string path); // returns all directory names in path public static string[] GetDirectories(string path, string searchPattern); public static DirectoryInfo GetParent(string path); // returns the parent directory public static string GetCurrentDirectory(); // returns current working directory public static void SetCurrentDirectory(string path); // sets current working directory public static string[] GetLogicalDrives(); // returns names of logical drives (e.g. “c:\”) public static DateTime GetCreationTime(string path); // returns creation date & time public static DateTime GetLastAccessTime(string path); public static DateTime GetLastWriteTime(string path); public static void SetCreationTime(string path, DateTime t); // sets creation date & time public static void SetLastAccessTime(string path, DateTime t); public static void SetLastWriteTime(string path, DateTime t); }
Klasa DirectoryInfo public sealed class DirectoryInfo : FileSystemInfo { //----- constructor public DirectoryInfo(string path); // path specifies the directory //----- properties public override string Name { get; } // returns directory name without the path public override bool Exists { get; } // indicates if this directory exists public DirectoryInfo Parent { get; } // returns the parent directory public DirectoryInfo Root { get; } // returns the root directory //----- methods public void Create(); // create a new directory, if it does not exist public DirectoryInfo CreateSubdirectory(string path); // creates a subdirectory public void MoveTo(string destDir); // moves this directory to destDir public void Delete(); // deletes this directory, if it is empty public void Delete(bool recursive); // deletes this directory and its contents public FileInfo[] GetFiles(); // returns all files in this directory public FileInfo[] GetFiles(string pattern); // returns matching files in this directory public DirectoryInfo[] GetDirectories(); // returns all directories in this directory public DirectoryInfo[] GetDirectories(string pattern); // returns all matching directories public FileSystemInfo[] GetFileSystemInfos(); // returns all files and directories public FileSystemInfo[] GetFileSystemInfos(string pattern); // returns files and directories for pattern public override ToString(); // returns the path given in the constructor }
DirectoryInfo - przykład using System; using System.IO; namespace DirectoryInfo1 { class Test { // list all files in the path to the current directory static void Main(string[] args) { DirectoryInfo d = new DirectoryInfo("."); while (d != null) { Console.WriteLine(d.FullName); FileInfo[] files = d.GetFiles(); foreach (FileInfo f in files) Console.WriteLine(" {0}, created: {1}", f.Name, f.CreationTime.ToString("D")); d = d.Parent; }
Klasa File public sealed class File { public static FileStream Open(string path, FileMode mode); public static FileStream Open(string path, FileMode m, FileAccess a); public static FileStream Open(string p, FileMode m, FileAccess a, FileShare s); public static FileStream OpenRead(string path); public static FileStream OpenWrite(string path); public static StreamReader OpenText(string path); // returns Reader for reading text public static StreamWriter AppendText(string path); // returns Writer for appending text public static FileStream Create(string path); // create a new file public static FileStream Create(string path, int bufferSize); public static StreamWriter CreateText(string path); public static void Move(string src, string dest); public static void Copy(string src, string dest); // copies file src to dest public static void Copy(string src, string dest, bool overwrite); public static void Delete(string path); public static bool Exists(string path); public static FileAttributes GetAttributes(string path); public static DateTime GetCreationTime(string path); public static DateTime GetLastAccessTime(string path); public static DateTime GetLastWriteTime(string path); public static void SetAttributes(string path, FileAttributes fileAttributes); public static void SetCreationTime(string path, DateTime creationTime); public static void SetLastAccessTime(string path, DateTime lastAccessTime); public static void SetLastWriteTime(string path, DateTime lastWriteTime); } FileInfo { FileInfo(string path) string Name string FullName string DirectoryName DirectoryInfo Directory int Length FileAttributes Attributes DateTime CreationTime bool Exists FileStream OpenRead() FileStream OpenWrite() FileStream Create() StreamReader OpenText() StreamWriter CreateText() Delete() CopyTo(string path) MoveTo(string path) ... }
Delegacje i zdarzenia Definiuje nowy typ referencyjny Zastępuje wskaźniki do funkcji Sygnatura i typ wartości zwracanej delegacji musi być identyczny jak funkcji którą reprezentuje Dwa typy delegacji System.Delegate System.MulticastDelegate Delegacja – metody Invoke - () Combine - += Remove - -= Obiekt, którego typ określony jest przez delegację, może reprezentować metodę lub metody - obiekt ten zawiera kolekcję metod Przy pomocy zmiennej zawierającej referencję do takiego obiektu możemy wywołać metodę(y), którą(e) dany obiekt reprezentuje. Zmienna w czasie swojego życia może zawierać referencję do różnych obiektów, więc przy pomocy tej samej zmiennej możemy wywołać różne metody lub różne grupy metod. Metoda może być reprezentowana przez obiekt, stąd może być ona również parametrem innej metody. Delegaty – deklaracja [atrybuty] [modyfikatory] delegate typ-wyniku nazwa ([parametry]); • dopuszczalne modyfikatory to new i modyfikatory dostepu. • dalej jak w deklaracji metody Delegaty – cechy • Delegaty mozna traktowac jak bezpieczne wskazniki do funkcji. • Sygnatura podstawianej funkcji musi byc taka jak deklaracja delegata. • Podstawiac mozna zarówno metody statyczne jak i niestatyczne (ang.instance methods) • Delegaty sa typami referencyjnymi • Obiekty typów delegowanych nie moga byc zmieniane (sa ang. immutable) delegate void myDelegate(int a, int b) ; myDelegate operation = new myDelegate(Add); operation += new myDelegate(Multiply); operation(1,2); 45
Delegacje Delegacja (delegate) - to specjalny typ danych umożliwiający „przechowywanie” referencji do metody o wspólnej sygnaturze. Cel delegacji : pozwala wywołać dowolną metodę (o sygnaturze zgodnej z sygnaturą delegacji) z dowolnego obiektu (~ jak wskaźniki do funkcji w C, C++); obsługa wywołań zwrotnych (callback) Obsługa zdarzeń Deklarowanie delegatów Sygnatura delegacji jest taka sama jak sygnatura metody, którą można wywołać za pośrednictwem tej delegacji; jest poprzedzona słowem kluczowym delegate. Dla delegacji nie ma znaczenia, który to jest obiekt, a nawet jakiego jest on typu. Jest to bardzo przydatne podczas obsługi wywołań zwrotnych (ang. callback), gdy wiadomo, jak będzie wyglądało wywołanie metody, ale nie wiadomo, z którego obiektu zostanie ona wywołana. Dzięki temu delegacje świetnie nadają się do obsługi zdarzeń i do tego celu są głównie używane w .NET. obiekt wywołujący konkretną metodę z konkretnego obiektu Można by odnieść wrażenie, że delegacje i interfejsy to podobne konstrukcje - i jest tak w istocie. Ale występują miedzy nimi dwie istotne różnice. Po pierwsze, klient komunikuje się bezpośrednio z obiektem implementującym dany interfejs, podczas gdy delegacja jest pośrednikiem miedzy klientem a obiektem. Po wtóre, delegacja reprezentuje jedna metodę, a definicja interfejsu może zawierać wiele powiązanych metod. Delegaty a interfejsy Delegaty i interfejsy są podobne w tym sensie, ze separują deklaracje od implementacji. Interfejsy sa użyteczne, gdy: • definiujemy zbiór powiązanych ze sobą metod • typowo wewnątrz klasy potrzebujemy jednej implementacji każdej z metod • spodziewamy się użyteczności tworzenia interfejsów i klas pochodnych Delegaty sa użyteczne, gdy: • potrzebujemy kilku implementacji danej metody wewnątrz klasy • dla obiektu wywołującego nie jest istotne, gdzie (w jakiej klasie) metoda została zdefiniowana. • ma sens podstawianie metod statycznych public delegate void AlarmEventHandler(object sender, AlarmEventArgs e); 46 46
Definiowanie i używanie delegacji Tworzenie i wywoływanie delegatów po zadeklarowaniu delegata należy stworzyć egzemplarz klasy delegata i przypisać do niego metodę następnie można wywołać określoną funkcję posługując się zmienną typu delegata. środowisko .NET obsługuje właściwe działanie delegacji Obiekt delegacji pozwala na pośrednie wywołanie metody zawiera referencję do metod wszystkie metody wywoływane przez ten sam delegat muszą mieć takie same parametry i zwracać ten sam typ Zaalokować Użycie: utworzyć obiekt delegacji i związać go z metodą, którą powinien wywoływać Praktycznie wygenerowanie zdarzenia oznacza wywoływanie metody obiektu, który chce być poinformowany o zajściu tego zdarzenia. Aby wygenerować zdarzenie w języku C#, VB należy zadeklarować je jako pole klasy. Deklaracja zawiera nazwę i sygnaturę (parametry i ich typy) zdarzenia. W platformie . NET sygnatury zdarzeń są zgodne z następującymi konwencjami: Pierwszy parametr jest typu System,.Object i stanowi obiekt, który wygenerował zdarzenie. Drugi parametr to instancja klasy, która jest pochodną klasy EventArgs. Przenosi ona informacje o zdarzeniu, które mogą być przydatne w kodzie jego obsługi. Nazwa parametru, który jest pochodną klasy EventArgs, kończy się na EventArgs. Deklarowanie zdarzenia. W C# Zdeklarować delegację. Delegacja stanowi zdeklarowanie metody i nadanie nazwy sygnaturze metody. Zgodnie z konwencją nazwa delegacji kończy się słowem EventHandler. Zadeklarować zdarzenie, którego typem jest w/w typ delegacji. Public delegate void CaughtOnFireEventHandler (object sender, CaughtOnFireEventArgs e); Public event CaughtOnFireEventHandler CaughtOnFire; Aby uczynić zdarzenie CaughtOnFire domyślnym zdarzeniem dla klasy trzeba dodać dla klasy kod atrybuty DefaultEvent [System.ComponentModel.DefaultEvent („CaughtOnFire”)] Generowanie zdarzeń W VB do generowania zdarzeń służy słowo kluczowe RiseEvent; W C# publiczna delegacja reprezentuje listę metod, które powinny być wywołane w chwili wygenerowania zdarzenia. Gdy wywoływana jest metoda CaughtOnFire, wywoływane są wszystkie metody dodane do delegacji 47 47
Przykład Deklaracja delegatu Deklaracja zmiennej delegatu delegate void Notifier (string sender); // ordinary method signature // with the keyword delegate Deklaracja zmiennej delegatu Notifier greetings; Przypisanie metody void SayHello(string sender) { Console.WriteLine("Hello from " + sender); } greetings = new Notifier(SayHello); // or just: greetings = SayHello; Delegate = Method Type Wywołanie delegacji greetings("John"); // invokes SayHello("John") => "Hello from John" 48
Assigning Different Methods Every matching method can be assigned to a delegate variable void SayGoodBye(string sender) { Console.WriteLine("Good bye from " + sender); } greetings = SayGoodBye; greetings("John"); // SayGoodBye("John") => "Good bye from John" Note A delegate variable can have the value null (no method assigned). If null, a delegate variable must not be called (otherwise exception). Delegate variables are first class objects: can be stored in a data structure, passed as a parameter, etc.
Multicast Delegates A delegate variable can hold multiple methods at the same time Notifier greetings; greetings = SayHello; greetings += SayGoodBye; greetings("John"); // "Hello from John" // "Good bye from John" greetings -= SayHello; greetings("John"); // "Good bye from John" Note If the multicast delegate is a function, the value of the last call is returned If the multicast delegate has an out parameter, the parameter of the last call is returned. ref parameters are passed through all methods. Delegaty – łaczenie • Delegaty mozna łaczyc – podstawic kilka metod pod to samo wystapienie • Połaczone metody wykonywane sa jedna po drugiej w zadanej kolejnosci • Jesli parametr był ref i uległ zmianie, to taki zmieniony parametr jest przekazywany do kolejnej metody • Jesli parametr był out (wbrew http://msdn2.microsoft.com/en-us/library/900fyy8e(VS.71).aspx może tak byc), to otrzymamy wartosc z ostatniego wywołania, podobnie z wartoscia zwracana (ang. return value). Jedna metoda może być dodana kilkukrotnie, wtedy usuniecie jej powoduje usuniecie ostatniego wystapienia. • Próba usuniecie nieistniejacej metody nie powoduje błedu. 50
Zdarzenia Źródło zdarzeń - ten który publikuje Odbiorca zdarzeń - subskrybent Wzorzec projektowy obserwator class NazwaKlasy { ... public event NazwaDelegacji NazwaZdarzenia; ... } Definiuje dwie metody publiczne Add Remove Definiuje jedną metodę protected Invoke Definicja zdarzenia powoduje, że w klasie tworzone jest prywatne pole, którego typ jest określony przez delegację oraz dwie metody umożliwiający sterowanie tym polem. Metody te odpowiednio umożliwiają podłączenie funkcji pod zdarzenie oraz odłączenie funkcji od zdarzenia. Wywołujemy je odpowiednio przy pomocy operatorów += i -= i mają taki sam modyfikator dostępu, jaki został użyty przy definicji zdarzenia. W celu wywołania metody lub metod podłączonych pod zdarzenie, wywołujemy zdarzenie przy pomocy jego nazwy, możemy jednak to zrobić tylko z metod klasy, gdzie zdarzenie zostało zdefiniowane, ponieważ pole jest prywatne. Przed wywołaniem zdarzenia należy sprawdzić, czy są podłączone do niego jakieś metody.
Event = Special Delegate Field class Model { public event Notifier notifyViews; public void Change() { ... notifyViews("Model"); } } class View { public View(Model m) { m.notifyViews += Update; } void Update(string sender) { Console.WriteLine(sender + " was changed"); } class Test { static void Main() { Model model = new Model(); new View(model); ... model.Change(); Why events instead of normal delegate variables? Only the class that declares the event can fire it (better encapsulation) Other classes may change event fields only with += or -= (but not with =) class Test { static void Main() { Model model = new Model(); new View(model); new View(model); ... model.Change(); } Why events instead of normal delegate variables? Only the class that declares the event can fire it (better encapsulation) Other classes may change event fields only with += or -= 52
Event Handling in the .NET Library In the .NET library delegates for event handling have the following signature delegate void SomeEvent (object source, MyEventArgs e); Result type: void 1. parameter: Sender of the event (type object) 2. parameter: event (subclass of System.EventArgs) public class EventArgs { … } public class EventArgs { public static readonly EventArgs Empty; } 53
Event Model in the .NET Framework Button1 Delegate this.button1.Click += new System.EventHandler(this.button1_Click); this.button1.Click += new System.EventHandler(this.button1_Click); private void button1_Click(object sender, System.EventArgs e) { … } Invokes the delegate Delegate calls the associated procedure Application-level Event Handling Web Forms Delegate Binds events to methods Can be bound to single or multiple methods When an event is recorded by an application The control raises the event by invoking the delegate for the event The delegate in turn calls the bound method Najczęściej wykorzystywanym zdarzeniem obiektu Page jest zdarzenie Load. Jest ono wywoływane w momencie gdy po stronie serwera aplikacja zbuduje wszystkie obiekty reprezentujące kontrolki i odtworzy ich stan na podstawie ViewState. Innym, często wykorzystywanym zdarzeniem jest zdarzenie Click obiektu Button służące do określenia co ma się stać po wciśnięciu przycisku przez użytkownika. Wiele obiektów udostępnia więcej niż jedno zdarzenie. Dla przykładu obiekt Button oprócz wspomnianego już Click udostępnia również zdarzenie Command. Jeśli obiekt udostępnia wiele zdarzeń, to jedno z nich jest najczęściej określane jako domyślne. Dla przykładu zdarzenie Clik jest domyślnym zdarzeniem obiektu Button. Dostęp do wszystkich zdarzeń jest możliwy po wybraniu obiektu, a następnie kliknięciu w oknie Properties przycisku Events Delegate Model Connect event sender and event receiver Single and multicast delegates Event Delegates Are Multicast Event Wiring Register event handler with event sender 54 54
Events Example: Component-Side Define the event signature as a delegate Define the event and firing logic public delegate void EventHandler(object sender, EventArgs e); public class Button { public event EventHandler Click; protected void OnClick(EventArgs e) { // This is called when button is clicked if (Click != null) Click(this, e); } }
Events Example: User-Side Define and register an event handler public class MyForm: Form { Button okButton; static void OkClicked(object sender, EventArgs e) { ShowMessage("You pressed the OK button"); } public MyForm() { okButton = new Button(...); okButton.Caption = "OK"; okButton.Click += new EventHandler(OkClicked);
Metody anonimowe Konstrukcja ta umożliwia utworzenie obiektu delegacji bez konieczności wcześniejszego definiowania metody delegate void MojaDelegacja(string s, int i); MojaDelegacja d1 = new MojaDelegacja(Test.F); d1("wywołanie", 1); Test t1 = new Test() { Nazwa = "Obiekt1" }; MojaDelegacja d2 = new MojaDelegacja(t1.G); d2("wywołanie", 2); MojaDelegacja d4 = delegate(string s1, int x) { Console.WriteLine("Metoda anonimowa z parametrami {0} oraz {1}", s1, x); }; d4("wywołanie", 5); class Test { public string Nazwa { set; get; } public static void F(string napis, int n) { Console.WriteLine("Metoda statyczna: {0} oraz {1}„,napis, n); } public void G(string napis, int n) { Console.WriteLine("Metoda zwykła: {0} oraz {1}" +", na rzecz obiektu {2}", napis, n, Nazwa);
Collection Initialisers 2.2.8 Metody rozszerzające, niejawna ścisła kontrola typów, typy anonimowe, wyrażenia lambda VB9 XML Literals Relaxed Delegates C# 3.0 Extension Methods Object Initialisers Anonymous Types Implicit Typing Lambdas Collection Initialisers Partial Methods Automatic Properties C# enhancements C# 3.0 Features Introduced for LINQ New features in .NET 3.5 for LINQ Extension Methods Anonymous Types Standard Query Operators Lambda Expressions Query expressions Implicitly typed local variables Object Initialization Syntax Collection Initialization Syntax Implicitly typed local variables (Domyślnie typowane zmienne lokalne) Extension methods (Metody rozszerzające) Lambda expressions (Wyrażenia Lambda) Object and collection initializers (Inicjalizatory obiektów i kolekcji) Anonymous types (Typy anonimowe) Implicitly typed arrays (Domyślnie typowane tablice)
Extension Methods Extension methods enable us to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type. For client code written in C# and Visual Basic, there is no apparent difference between calling an extension method and the methods that are actually defined in a type. The most common extension methods are the LINQ standard query operators.
Metody rozszerzające (Extension Methods) Dodawanie nowych metod do istniejących klas bez modyfikacji ich kodu static class RozszerzenieDouble{ public static double Potega(this double x, int n){ double p = 1; for (int i = 0; i < (n < 0 ? -n : n); i++) p *= x; return n < 0 ? 1 / p : p; } class Program{ static void Main(string[] args){ double a = 2; Console.WriteLine(a.Potega(2)); Console.WriteLine((2.0).Potega(-2)); }
Implicitly typed local variables C# 3.5 introduces a new var keyword that can be used in place of the type name when performing local variable declarations. The var keyword always generates a strongly typed variable reference. Rather than require the developer to explicitly define the variable type, the var keyword instead tells the compiler to infer the type of the variable from the expression used to initialize the variable when it is first declared. The var keyword can be used to reference any type in C#. Example: var name = "ABC XYZ"; string name = "ABC XYZ"; var age = 33; int age = 33; var male = true; bool male = true The compiler will infer the type of the "name", "age" and "male" variables based on the type of their initial assignment value. Limitations: only local variables the variable must be initialized in the declaration the initialization expression must not be an anonymous function without casting the variable must not be initialized to null only one variable can be declared in the statement the type must be compile-time type Implicit Typing The CLR actually never knows that the var keyword is being used - from its perspective there is absolutely no difference between the above two code examples. C# 3.5 introduces a new var keyword that can be used in place of the type name when performing local variable declarations. The var keyword always generates a strongly typed variable reference. Rather than require the developer to explicitly define the variable type, the var keyword instead tells the compiler to infer the type of the variable from the expression used to initialize the variable when it is first declared. The var keyword can be used to reference any type in C#. Example: var name = "ABC XYZ"; var age = 33; var male = true; The compiler will infer the type of the "name", "age" and "male" variables based on the type of their initial assignment value. This means it will generate IL that is absolutely identical to the code below: string name = "ABC XYZ"; int age = 33; bool male = true; Limitations: only local variables the variable must be initialized in the declaration the initialization expression must not be an anonymous function without casting the variable must not be initialized to null only one variable can be declared in the statement the type must be compile-time type
Typy implikowane (implicit typing) var a = 4; // int var b = ‘M'; // char var c = 1234L; // long var d = "Ala ma kota"; // string … a = 4654434L; // long -> int d = 4.5; // błąd tylko zmienne lokalne w funkcjach - nie mogą być składowymi klas (3.0) Niejawna ścisła kontrola typów
Anonymous Types Anonymous types provide a convenient way to encapsulate a set of read-only properties into a single object without having to first explicitly define a type. The type name is generated by the compiler and is not available at the source code level. The type of the properties is inferred by the compiler. The following example shows an anonymous type being initialized with two properties called Price and Message. var v = new { Price = 105, Message = “XYZ” }; Anonymous types are typically used in the select clause of a query expression to return a subset of the properties from each object in the source sequence. Anonymous types are created by using the new operator with an object initializer.
Anonymous Types public class Customer { public string Name; public Address Address; public string Phone; public List<Order> Orders; … } public class Contact { public string Name; public string Phone; } Customer c = GetCustomer(…); Contact x = new Contact { Name = c.Name, Phone = c.Phone }; Customer c = GetCustomer(…); var x = new { Name = c.Name, Phone = c.Phone }; Obiekty bez definiowania ich typu (klasy lub struktury) Customer c = GetCustomer(…); var x = new { c.Name, c.Phone };
Lambda Expressions A lambda expression is an anonymous function that can contain expressions and statements, and can be used to create delegates or expression tree types. All lambda expressions use the lambda operator =>, which is read as "goes to". The left side of the lambda operator specifies the input parameters (if any) and the right side holds the expression or statement block. Example: var maxAge = objPerson.Max(p => p.Age).ToString(); One of the things that make Lambda expressions particularly powerful from a framework developer's perspective is that they can be compiled as either a code delegate (in the form of an IL based method) or as an expression tree object which can be used at runtime to analyze, transform or optimize the expression. Lambda Expressions are super useful when writing LINQ query expressions - since they provide a very compact and type-safe way to write functions that can be passed as arguments for subsequent evaluation. Anonimowa funkcja Używa operatora =>, który czytamy prowadzi do Lewa strona definiuje parametry wejściowe Prawa strona definiuje wyrażenie Często używana jak parametr metody
Wyrażenia lambda (lista_argumentów) => {instrukcje_definiujące_funkcję}; Delgacja1 wyr1 = () => { Console.WriteLine("Funkcja bezparametrowa"); return 2.0; }; Console.WriteLine(wyr1()); delegate double Delgacja1(); delegate int Delgacja2(int x, int y); delegate double Delgacja3(double x); Delgacja2 wyr2 = (a, b) => a + b; Console.WriteLine("3 + 4 = {0}", wyr2(3, 4)); Delgacja3 wyr3 = a => a * a; Console.WriteLine("Kwadrat liczby 3 wynosi {0}", wyr3(3));
2.2.9 Refleksja i atrybuty System.Reflection, System.Attribute Uzyskiwanie informacji o typie w czasie działania programu Przestrzeń nazw System.Reflection Klasa System.Type Dziedziczy po MemberInfo Właściwości (IsAbstract, IsArray, IsNestedPrivate, IsNestedPublic, ...) Metody (GetType(), GetConstructors(), GetEvents(), GetMethods(), FindMembers(), …) - zwracają obiekty, których typ jest określony przez jedną z klas znajdujących się w przestrzeni nazw System.Reflection Class Description Assembly represents an assembly, which is a reusable, versionable, and self-describing building block AssemblyName describes an assembly's unique identity in full EventInfo discovers the attributes of an event and provides access to event metadata FieldInfo discovers the attributes of a field and provides access to field metadata MemberInfo obtains information about the attributes of a member and provides access to member metadata MethodInfo ParameterInfo discovers the attributes of a parameter and provides access to parameter metadata … The System.Reflection namespace contains types that retrieve information about assemblies, modules, members, parameters, and other entities in managed code by examining their metadata. Klasa System.Type Uwaga: Klasy *Info mają nadpisaną metodę ToString() zwracającą sygnaturę żądanego elementu. Metody klasy Type, zwracają obiekty, których typ jest określony przez jedną z klas znajdujących się w przestrzeni nazw System.Reflection. W przestrzeni nazw System.Reflection znajdują się między innymi definicje takich klas jak: EventInfo, FieldInfo, MemberInfo itd. I tak np.: metoda GetEvents() klasy Type, zwraca tablicę obiektów typu EventInfo. System.Type -Klasa abstrakcyjna – nie można tworzyć obiektów przy pomocy operatora new Type Class -Represents type declarations: class types, interface types, array types, value types, enumeration types, type parameters, generic type definitions, and open or closed constructed generic types. When you call Type.GetType() in order to obtain metadata descriptions of generic types, you must make use of a special syntax involving a “back tick” character (`) followed by a numerical value that represents the number of type parameters the type supports: System.Collections.Generic.List`1 System.Collections.Generic.Dictionary`2. Be aware that each of the XXXInfo types (MethodInfo, PropertyInfo, EventInfo, etc.) have overridden ToString() to display the signature of the item requested. http://msdn.microsoft.com/en-us/library/system.reflection.aspx Programy wykorzystujące refleksję: Ildasm, reflector.exe (http://www.red-gate.com/products/reflector/) 67
Reflection Class Hierarchy Permits access to meta-information of types at run-time System.Reflection allows: Getting meta-information about assemblies, modules and types Getting meta-information about the members of a type Dynamic creation of instances of a type at run-time Search for methods and their dynamic invocation at run-time Accessing values of properties and fields of an object Design of new types at run time - namespace System.Reflection.Emit Assembly GetTypes() Type * BaseType * Interfaces GetFields() * FieldInfo MemberInfo GetMethods() * MethodInfo MethodBase GetConstructors() * ConstructorInfo GetProperties() * PropertyInfo GetEvents() * EventInfo
Class Type Type used for meta-description of all types in the run-time system Provides access to the meta-information about its members public abstract class Type : MemberInfo, IReflect { public abstract string FullName {get;}; public abstract Type BaseType {get;}; public Type[] GetInterfaces(); public bool IsAbstract {get;}; public bool IsClass {get;}; public bool IsPublic {get;}; … public ConstructorInfo[] GetConstructors(); public virtual EventInfo[] GetEvents(); public FieldInfo[] GetFields(); public MethodInfo[] GetMethods(); public PropertyInfo[] GetProperties(); ... Type name Direct base type List of implemented interfaces Properties of type Getting constructors, events, fields, methods, properties Optionally parameterized by BindingFlags
Klasa System.Type Metoda Object.GetType() MojaKlasa obj = new MojaKlasa(); Type t = obj.GetType(); operator typeof() Type t = typeof(NazwaTypu); metoda statyczna klasy Type - GetType() using System; public class MyBaseClass: Object { } public class MyDerivedClass: MyBaseClass { } public class Test { public static void Main() { MyBaseClass myBase = new MyBaseClass(); MyDerivedClass myDerived = new MyDerivedClass(); Console.WriteLine("mybase: Type is {0}", myBase.GetType()); Console.WriteLine("myDerived: Type is {0}", myDerived.GetType()); … Metoda Object.GetType() – na rzecz obiektu Nie trzeba tworzyć obiektu, ale typ musi być znany już podczas kompilacji (nie string) Otrzymywanie "obiektu" klasy System.Type Type t = typeof(NazwaTypu); Wprawdzie przy pomocy operatora typeof nie trzeba tworzyć obiektu, ale nazwa typu, o którym chcemy zasięgnąć informacji, musi być znana w czasie kompilacji. Warto zwrócić uwagę, że metoda GetType ma przeciążaną wersję, gdzie dodatkowo możemy podać, czy w wypadku, gdy podany typ nie zostanie znaleziony rzucić wyjątek oraz czy przy wyszukiwaniu brać pod uwagę wielkość liter. public static Type GetType( string nazwaTypu, Boolean czyWielkoscLiter, Boolean czyWyjatek) using System; public class MyBaseClass: Object { } public class MyDerivedClass: MyBaseClass { } public class Test { public static void Main() { MyBaseClass myBase = new MyBaseClass(); MyDerivedClass myDerived = new MyDerivedClass(); object o = myDerived; MyBaseClass b = myDerived; Console.WriteLine("mybase: Type is {0}", myBase.GetType()); Console.WriteLine("myDerived: Type is {0}", myDerived.GetType()); Console.WriteLine("object o = myDerived: Type is {0}", o.GetType()); Console.WriteLine("MyBaseClass b = myDerived: Type is {0}", b.GetType()); } 70
Refleksja - przykład using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace Page_227 { class SomeClass public int Field1; public int Field2; public void Method1() } public int Method2() return 1; class Program { static void Main() Type t = typeof( SomeClass ); FieldInfo[] fi = t.GetFields(); MethodInfo[] mi = t.GetMethods(); foreach ( FieldInfo f in fi ) Console.WriteLine( "Field : {0}", f.Name ); foreach ( MethodInfo m in mi ) Console.WriteLine( "Method: {0}", m.Name ); }
Class Assembly Class Assembly loads assemblies and their meta-data Provides access to its meta-data Klasa System.Reflection.Assembly Load() LoadFrom() - pozwala wprowadzić ścieżkę bezwzględną public class Assembly { public static Assembly Load(string name); public virtual string FullName {get;} public virtual string Location {get;} public virtual MethodInfo EntryPoint {get;} public Module[] GetModules(); public virtual Type[] GetTypes(); public virtual Type GetType(string typeName); public object CreateInstance(string typeName); ... } Loading an assembly Name, storage location, entry point of the assembly Getting modules and all in the assembly defined types Getting type with name typeName Creation of an object of type typeName Dynamiczny ładowany zestaw If you wish to make ExternalAssemblyReflector more flexible, you can update your code to load the external assembly using Assembly.LoadFrom() rather than Assembly.Load(). W czasie uruchomienia, program może wczytad zestawy z wybranych katalogów. Następnie przejrzy typy w nich zaimplementowane pod względem pewnych kryteriów (np. implementacji interfejsów). W przypadku gdy typ spełniający dane kryteria istnieje, utworzyd jego instancję, a następnie wywoład metodę na rzecz danej instancji. Zwródmy uwagę, że nie musimy w czasie tworzenia programu znad nazwy danego typu. Assembly.LoadFrom("c:\\Katalog\\Przykladowy.Zestaw.dll"); Assembly a = Assembly.Load(@"NazwaZestawu, Version=1.0.982.23972, PublicKeyToken=null, Culture="""); Assembly.LoadFrom("c:\\Katalog\\Przykladowy.Zestaw.dll"); Assembly a = Assembly.Load(@"NazwaZestawu, Version=1.0.982.23972, PublicKeyToken=null, Culture=""");
Refleksja – przykład 2 namespace Hello { using System; public class HelloWorld { public static void Main(string[] args) { Console.WriteLine("HelloWorld"); } public override string ToString() { return "Example HelloWorld"; // Loading the assembly "HelloWorld.exe": Assembly a = Assembly.Load("HelloWorld"); // Print all existing types in a given assembly Type[] types = a.GetTypes(); foreach (Type t in types) Console.WriteLine(t.FullName); // Print all existing methods of a given type Type hw = a.GetType("Hello.HelloWorld"); MethodInfo[] methods = hw.GetMethods(); foreach (MethodInfo m in methods) Console.WriteLine(m.Name); Hello.HelloWorld Example Reflection GetType ToString Equals GetHashCode
Późne wiązanie – ładowanie klasy on-line Klasa System.Activator Plug-iny Assembly a = null; a = Assembly.Load("NazwaZestawu"); Type typ = a.GetType("Przestrzen.Klasa"); object obj = Activator.CreateInstance(typ); MethodInfo mi = typ.GetMethod("NazwaMetody"); mi.Invoke(obj, null); //null – bez argumentów object[] parametry = new object[2]; parametry[0] = "Napis"; parametry[1] = 4; mi = typ.GetMethod("MetodaZParametrami"); mi.Invoke(obj, parametry); Mając wczytany zestaw, w celu pobrania wszystkich typów (również zagnieżdżonych i prywatnych) w nim zdefiniowanych możemy użyd metody GetTypes() klasy Assembly: Assembly a = Assembly.LoadFrom("c:\\Katalog\\Przykladowy_Zestaw.dll"); Type[] typy = a.GetTypes(); Po wczytaniu zestawu możemy utworzyd obiekt dowolnego typu zawartego w danym zestawie. Służy do tego klasa System.Activator i jego statyczna metoda CreateInstance. Po utworzeniu obiektu możemy na rzecz niego wywoład metodę. 74
Atrybuty - System.Attribute Atrybuty - mechanizm, pozwalający na dodanie dodatkowej informacji opisującej poszczególne elementy kodu – metadane. Dostęp do metadanych jest możliwy dzięki mechanizmowi refleksji, Predefiniowane atrybuty Tworzenie własnych atrybutów Przykłady: [Conditional] – powoduje, że wywołanie metody jest zależne, od zdefiniowania jakiegoś identyfikatora przy pomocy dyrektywy #define. Atrybut ten jest często wykorzystywany do metod diagnostycznych, sprawdzających pewne asercje, wypisujących że zostało osiągnięte pewne miejsce w naszym programie. [Serializable] – stosowany do klas, umożliwia zapisywanie stanu obiektu danej klasy do strumienia. [WebMethod] – dodanie tego atrybutu do metody klasy reprezentującej usługę sieciową (XML Web service), powoduje że dana metoda jest dostępna dla klientów danej usługi. [DllImport] – pozwala wywołać dowolną eksportowaną funkcję z biblioteki DLL kodu niezarządzanego. Ten atrybut dodajemy do sygnatury metody, która niejako stanowi punkt wejścia do żądanej metody … An attribute is a language construct that allows you to add metadata to a program’s assembly. zadeklarowanie co chcemy uzyskad, opis tego co nas interesuje, co ma byd zrobione, ale nie jak ma byd zrobione. [CLSCompliant] – określa czy dany element języka (klasa, metoda, zestaw) jest zgodny ze wspólną specyfikacją języka (ang. Common Language Specification — CLS). [Obsolete] – ten atrybut stosujemy dla tych elementów biblioteki, które prawdopodobnie w przyszłych jej wersjach zostaną z niej usunięte. W przypadku, gdy w swoim programie użyjemy elementu opatrzonego takim atrybutem, kompilator zgłosi ostrzeżenie. 75
Atrybuty Metadane - adnotacje do kodu Klasy dziedziczące po System.Attribute Mogą opisywać podzespoły, metody, typy, klasy interfejsy, pola, zdarzenia, właściwości Nazwa jest domyślnie rozszerzana przez przyrostek Attribute [Serializable] //atrybut dotyczy klasy NaszaKlasa public class NaszaKlasa { [Obsolete("Użyj innej metody")] //atrybut dotyczy metody Metoda1 public void Metoda1() { } } [assembly: AssemblyCompany("Nazwa firmy")] [NazwaAtrybutu(parametry_pozycyjne,nazwany_parametr=wartość, ...)] Wewnątrz nawiasów kwadratowych umieszczamy nazwę atrybutu. Z większością atrybutów związane są parametry, które umieszczamy wewnątrz nawiasów okrągłych. Mamy dwa rodzaje parametrów.: parametry pozycyjne oraz parametry nazwane. Parametry pozycyjne zazwyczaj zawierają istotne dane dla danego atrybutu. Łączenie wartości z danym parametrem, odbywa się dla nich, tak ja przy przekazywaniu argumentów do metody, brana jest pod uwagę kolejnośd. Parametry nazwane występują zawsze po parametrach pozycyjnych, są opcjonalne, składają się z pary: nazwa parametru oraz wartości którą należy temu parametrowi nadad, rozdzielonych znakiem równości. Chcąc dodad metadane do zestawu, w nawiasach kwadratowych umieszczamy słowo assembly zakooczone dwukropkiem, a dopiero później nazwę atrybutu: Visual Studio atrybuty dotyczące zestawu, umieszcza w pliku AssemblyInfo.cs, który znajduje się w folderze Properties danego projektu. \Properties\AssemblyInfo.cs 76
Defining Your Own Attributes Declaration [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited=true)] class CommentAttribute : Attribute { string text, author; public string Text { get {return text;} } public string Author { get {return author;} set {author = value;} } public Comment (string text) { this.text = text; author ="HM"; } } Usage [Comment("This is a demo class for Attributes", Author="XX")] class C { ... } Querying the attribute at runtime class Attributes { static void Main() { Type t = typeof(C); object[] a = t.GetCustomAttributes(typeof(Comment), true); foreach (Comment ca = a) { Console.WriteLine(ca.Text + ", " + ca.Author); }
Atrybuty - przykład #define DoTrace using System; using System.Diagnostics; namespace AttributesConditional { class Program [Conditional( "DoTrace" )] static void TraceMessage( string str ) Console.WriteLine( str ); } static void Main() TraceMessage( "Start of Main" ); Console.WriteLine( "Doing work in Main." ); TraceMessage( "End of Main" );
2.2.10 Przetwarzanie dokumentów XML XmlReader: Reading XML data XmlDocument, XmlNode: Object model for XML data (DOM) XmlWriter: Wrting XML data XPathNavigator: XPath selections XslTransform: Transformation of XML documents Processing XML Data
Odczyt i zapis XML w .NET Abstrakcyjne klasy XmlReader i XmlWriter po których dziedziczą: XmlTextReader – zapewnia odczyt danych XML z pliku, sprawdzając czy dokument jest dobrze uformowany, lecz bez możliwości wykorzystania W3C Schema. Odczytując dokument jesteśmy w stanie poruszać się tylko do przodu, nie ma możliwości cofnięcia się do poprzednich linii XmlNodeReader – zapewnia dokładnie tą samą funkcjonalność co poprzednik, lecz odczytuje dane tylko z określonego węzła drzewa. XmlValidationReader – to samo co XmlTextReader lecz z możliwością sprawdzenia poprawności danych względem reguł zawartych w W3C Schema. XmlTextWriter – zapewnia zapisywanie danych do pliku XML, także bez możliwości cofnięcia się do poprzednich linii XmlTextReader xtw = new XmlTextReader("Sample1.xml"); while(xtw.Read()) { Console.WriteLine(xtw.NodeType.ToString() +":"+xtw.Name +":"+xtw.Value); if(xtw.HasAttributes) for(int i=0;i<xtw.AttributeCount;i++) xtw.MoveToAttribute(i); } xtw.MoveToElement();
Odczyt i zapis XML w .NET (2) XmlTextWriter xtw = new XmlTextWriter("Sample2.xml",null); xtw.Formatting=Formatting.Indented; xtw.WriteStartDocument(); xtw.WriteComment("to plik utworzony poprzez obiekt XmlTextWriter"); xtw.WriteStartElement("Książki"); xtw.WriteStartElement("Ksiązka"); xtw.WriteAttributeString("wydawnictwoId","3"); xtw.WriteStartElement("ISBN"); xtw.WriteString("777-7777-777"); xtw.WriteEndElement(); xtw.WriteEndDocument(); xtw.Close();
Struktura dokumentu DOM DocumentElement: wierzchołek drzewa dokumentu; Node: każdy z węzłów (w tym elementy); Element a węzeł: element to jeden z występujących w dokumencie DOM rodzajów węzłów (m.in. węzły tekstowe, atrybutowe). Węzły elementowe: zwykle zawierają węzły potomne: elementowe, tekstowe lub obu rodzajów. Węzły atrybutowe: informacja podległa konkretnemu węzłowi elementowemu. Nie są określane jako potomkowie. Węzeł dokumentu: rodzic dla wszystkich pozostałych węzłów. CData: odpowiada sekcji znakowej, zawierającej zawartość nie przeznaczoną do przetwarzania; Comment: (komentarz); ProcessingInstruction (instr. przetwarzania); DocumentFragment: stosowany jako węzeł roboczy przy budowaniu lub rekonstruowaniu dokumentu XML. Ponadto: Entity, EntityReference, Notation.
DOM - Przykład Przykład wykorzystujący klasę XmlDocument środowiska .NET Framework do pobrania nazwy artysty i tytułu z pierwszego elementu compact-disc, znajdującego się w elemencie items using System; using System.Xml; public class Test{ public static void Main(string[] args){ XmlDocument doc = new XmlDocument(); doc.Load("test.xml"); XmlElement firstCD = (XmlElement)doc.DocumentElement.FirstChild; XmlElement artist = (XmlElement) firstCD.GetElementsByTagName("artist")[0]; XmlElement title = (XmlElement) firstCD.GetElementsByTagName("title")[0] Console.WriteLine("Artysta={0}, Tytuł={1}", artist.InnerText, title.InnerText); } }
XPath w.NET - przykład Klasy XPathDocument i XPathNavigator Przykład, w którym zastosowano klasę XPathDocument do pobrania nazwy artysty i tytułu z pierwszego elementu compact-disc, znajdującego się w elemencie items using System; using System.Xml.XPath; public class Test{ public static void Main(string[] args){ XPathDocument doc = new XPathDocument("test.xml"); XPathNavigator nav = doc.CreateNavigator(); XPathNodeIterator iterator = nav.Select("/items/compact-disc[1]/artist | /items/compact-disc[1]/title"); iterator.MoveNext(); Console.WriteLine("Artysta={0}", iterator.Current); Console.WriteLine("Tytuł={0}", iterator.Current); }
procesor transformacji Przekształcanie dokumentów XML: Język XSL (eXtensible Stylesheet Language) XML – opisuje strukturę i semantykę, nie opisuje sposobów prezentacji informacji Oddzielenie warstwy informacji od prezentacji uzyskuje się poprzez dołączanie arkuszu stylów. CSS - Cascading Style Sheets (level1 i Level2) - związany głównie z HTML XSL - eXtensible Stylesheet Language - opracowany dla dokumentów XML. Obejmuje: XSLT (XSL Transformations) - język przekształceń o charakterze deklaratywnym, oparty na regułach przekształceń (rule-based) drzew XML XSL FO (XSL Formating Objects) – język opisu formatu Koncepcja realizacji przekształceń Dokument XML Arkusz stylistyczny XSL XSLT (lub HTML, WML, tekst, … - zmiana struktury dokumentu XML - utworzenie arkusza XSLT do przetwarzania innych dokumentów - obudowanie danych z pliku XML informacjami o sposobie prezentacji procesor transformacji
XSLT w .NET - przykład <?xml version="1.0" encoding="utf-8" ?> <Towar> <Książka> <Cena></Cena> <Autor></Autor> </Książka> </Towar> Postać dokumentu wyjściowego: Szablon XSLT: <?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform"> <xsl:template match ="/"> <Towary> <xsl:apply-templates/> </Towary> </xsl:template> <xsl:template match ="Książka"> <Książka> <Cena> <xsl:value-of select ="Cena"/> </Cena> <Autor> <xsl:value-of select ="Autor/Imie"/><xsl:text> </xsl:text> <xsl:value-of select ="Autor/Nazwisko"/> </Autor> </Książka> </xsl:stylesheet> Sample3.xslt
XSLT w .NET – przykład (2) Zastosowanie szablonu: XPathDocument xpd = new XPathDocument("Sample1.xml"); FileStream fs = new FileStream("wysyłka.xml",FileMode.OpenOrCreate,FileAccess.Write,FileShare.ReadWrite); XslTransform xsl = new XslTransform(); xsl.Load("Sample3.xslt"); xsl.Transform(xpd,null,fs); fs.Close(); Dokument wyjściowy XML: <?xml version="1.0" encoding="utf-8"?> <Towary> <Książka> <Cena>981</Cena> <Autor>Tom Archer</Autor> </Książka> ... </Towary> wysyłka.xml
2.2.11 Serializacja obiektów Serializacja jest procesem zamiany obiektów lub kolekcji obiektów na strumień bajtów. Wykorzystywana do zapisywania stanu systemu w celu późniejszego odtworzenia lub przy przesyłaniu obiektów przez sieć. Deserializacja jest procesem odwrotnym. Serializacja binarna – klasa BinaryFormatter do serializowania struktur danych do postaci strumienia binarnego. Tworzy dokładną reprezentacje obiektu, zawierającą również informacja o typach. Serializacja protokołu SOAP – klasa SoapFormatter od serializowania typu danych do postaci strumienia XML zgodnego ze standardami protokołu SOAP Serializacja do formatu XML – klasa XMLSerializer do serializowania danych do postaci dokumentu XML Proces konwersji stanu obiektu do postaci która może być zachowana lub przesłana Zastosowanie: łatwe, standard, używana przy przesyłaniu obiektów przez wartość z jednej domeny aplikacji do drugiej, jest używana przez szereg technologi np.: .NET remoting, XML Web Services, Windows Communication Foundation Dwa rodzaje serializacji: Binarna Serializacja binarna tworzy dokładną reprezentację obiektu. XML-owa. Serializacja XML-owa serializuje publiczne zmienne i właściwości. Wykorzystanie atrybutów przypisanych do zmiennych lub właściwości.
Serializacja binarna using System; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.IO; // Store Chair objects in a Hashtable Hashtable ht = new Hashtable(); // Chair and Upholstery must have [Serializable] attribute Chair ch = new Chair(100.00D, "Broyhill", "10-09"); ch.myUpholstery = new Upholstery("Cotton"); ht.Add("10-09", ch); // Add second item to table ch = new Chair(200.00D, "Lane", "11-19"); ch.myUpholstery = new Upholstery("Silk"); ht.Add("11-19", ch); Deklaracje klas Chair i Upholstery musza zawierać atrybut [Serializable] Proces serializacji tworzy graf obiektów – głęboka kopia obiektów. Możliwość selektywnego wyłączania składowych klas [NonSerialized] public Upholstery myUpholstery; Nadaje się do przechowywania stanu systemu, komunikacji przez Remoting // (1) Serialize // Create a new file; if file exits it is overwritten FileStream fs= new FileStream("c:\\chairs.dat", FileMode.Create); BinaryFormatter bf= new BinaryFormatter(); bf.Serialize(fs,ht); fs.Close(); // (2) Deserialize binary file into a Hashtable of objects ht.Clear(); // Clear hash table. fs = new FileStream("c:\\chairs.dat", FileMode.Open); ht = (Hashtable) bf.Deserialize(fs); // Confirm objects properly recreated ch = (Chair)ht["11-19"]; Console.WriteLine(ch.myUpholstery.Fabric); // "Silk" fs.Close();
Serializacja binarna (2) Serialization and Deserialization Events Event Attribute Description OnSerializing [Serializing] Occurs before objects are serialized. Event handler is called for each object to be serialized. OnSerialized [Serialized] Occurs after objects are serialized. Event handler is called once for each object serialized. OnDeserializing [Deserializing] Occurs before objects are deserialized. Event handler is called once for each object to be deserialized. OnDeserialized [Deserialized] Occurs after objects have been deserialized. Event handler is called for each deserialized object. public class Chair { // other code here [OnDeserialized] void OnDeserialized(StreamingContext context) // Edit vendor name after object is created if (MyVen == "Lane") MyVen = "Lane Bros."; }
XML Serialization in the .NET Framework System.Xml.Serialization Serializacja XML-owa Serializacja XML-owa serializuje publiczne zmienne i właściwości. Wykorzystanie atrybutów przypisanych do zmiennych lub właściwości jako znaczników w dokumencie XML. XmlRootAttribute XmlElementAttribute XmlAttributeAttribute XmlArrayAttribute XmlArrayItemAttribute Deserializacja XML-owa zbudowanie hierarchii obiektów na podstawie dokumentu XML. When implementing Web services by using Microsoft Visual Studio® .NET and the .NET Framework, it is convenient to define the service interface (the methods to be exposed) in terms of the native .NET data types. The native serialization format for the .NET data types is XML. However, sometimes the default mapping of objects to elements is not what you require. Therefore you must instruct the XML serializer on how to convert an object graph into the XML document structure that you require. The System.Xml.Serialization namespace in the .NET Framework provides classes that you can use to modify the way objects are serialized. Every XML document must have a single root element. The XmlRoot attribute allows you to control how the root element is generated by setting certain properties. For example, you can specify the name of the generated XML element by setting the ElementName property. You can apply the XmlRoot attribute to classes only You can apply the XmlElement attribute to public fields or public properties to control the characteristics of XML elements, such as the element name and namespace. If you apply the XmlElement attribute to a field or property that returns an array, the items in the array are generated as a sequence of XML elements. However, if you do not apply the XmlElement attribute to such a field or property, the items in the array are generated as a sequence of child elements, nested under an element, which is named after the field or property. By default, the XML serializer serializes public fields and properties as XML elements. When you apply the XmlAttribute attribute to a public field or property, the XML serializer serializes the member as an XML attribute. XML attributes can only be simple types. Therefore, you can apply the XmlAttribute attribute only to public fields or properties that return a primitive type. When you apply the XmlArray attribute class to a class member, the XML serializer generates a nested sequence of XML elements from that member. For example, if a class that is to be serialized represents a bank’s customer, then you can generate an array of accounts that the customer owns by applying the XmlArray attribute to a public field that returns an array of objects that represent the accounts. If you apply the XmlArray attribute to a field or property that returns an array, then by default, the name of the generated XML element is derived from the member identifier. However, by setting the ElementName property of the XmlArray attribute, you can change the name of the generated XML element. Use of POST/GET versus SOAP Currently, when you use [return:XmlArrayItem], the name of the array item is modified when you use SOAP, but not when you use GET or POST. Therefore, the generated XML document will be different depending on whether the Web service consumer uses POST/GET or SOAP. The following code shows how to control the names of the XML elements that are emitted when an array is serialized. Property serialization When an object is serialized, only public read/write properties are serialized. In other words, there is no way to serialize a read-only property (a property with only a get accessor). xsd.exe – do generowania klas na podstawie schematu
Przykład: Using XmlSerializer to Create an XML File using System.Xml; using System.Xml.Serialization; // other code here ... public class movies { public movies() // Parameterless constructor is required { } public movies(int ID, string title, string dir,string pic, int yr, int movierank) movieID = ID; movie_Director = dir; bestPicture = pic; rank = movierank; movie_Title = title; movie_Year = yr; } // Public properties that are serialized public int movieID get { return mID; } set { mID = value; } public string movie_Title get { return mTitle; } set { mTitle = value; } public int movie_Year { get { return mYear; } set { mYear = value; } } public string movie_Director get { return mDirector; } set { mDirector = value; } public string bestPicture get { return mbestPicture; } set { mbestPicture = value; } [XmlElement("AFIRank")] public int rank get { return mAFIRank; } set { mAFIRank = value; } private int mID; private string mTitle; private int mYear; private string mDirector; private string mbestPicture; private int mAFIRank;
Przykład: Using XmlSerializer to Create an XML File // (1) Create array of objects to be serialized movies[] films = {new movies(5,"Citizen Kane","Orson Welles", "Y", 1941,1 ), new movies(6,"Casablanca","Michael Curtiz", "Y", 1942,2)}; // (2) Create serializer // This attribute is used to assign name to XML root element XmlRootAttribute xRoot = new XmlRootAttribute(); xRoot.ElementName = "films"; xRoot.Namespace = "http://www.corecsharp.net"; xRoot.IsNullable = true; // Specify that an array of movies types is to be serialized XmlSerializer xSerial = new XmlSerializer(typeof(movies[]), xRoot); string filename=@"c:\oscarwinners.xml"; // (3) Stream to write XML into TextWriter writer = new StreamWriter(filename); xSerial.Serialize(writer,films);
Przykład: wynik serializacji <?xml version="1.0" standalone="yes"?> <films> <movies> <movie_ID>5</movie_ID> <movie_Title>Citizen Kane </movie_Title> <movie_Year>1941</movie_Year> <movie_DirectorID>Orson Welles</movie_DirectorID> <bestPicture>Y</bestPicture> <AFIRank>1</AFIRank> </movies> <movie_ID>6</movie_ID> <movie_Title>Casablanca </movie_Title> <movie_Year>1942</movie_Year> <movie_Director>Michael Curtiz</movie_Director> </films>
Atrybuty kontrolujące XML serializację Elementy tworzone na podstawie serializowanych obiektów klasy domyślnie otrzymują nazwy zgodne z nazwami reprezentowanych właściwości. Istnieją Atrybuty serializacji, za pomocą których możemy przykrywać domyślne nazwy elementów. Poprzedni przykład - nazwa właściwości rank została zastąpiona wyrażeniem AFIRank Serialization attributes XmlElement Zmienna lub właściwość zostanie podczas serializacji zapisana jako węzeł XmlAttribute Is attached to a property or field and causes it to be rendered as an attribute within an element. Example: XmlAttribute("movie_ID")] Result: <movies movie_ID=„5”> XmlIgnore Causes the field or property to be excluded from the XML. XmlText Causes the value of the field or property to be rendered as text. No elements are created for the member name. Example: [XmlText] public string movie_Title{ Result: <movies movie_ID=„5”>Citizen Kane