Pobieranie prezentacji. Proszę czekać

Pobieranie prezentacji. Proszę czekać

Waldemar Bartyna 1 Programowanie zaawansowane Zaawansowane konstrukcje języka C#

Podobne prezentacje


Prezentacja na temat: "Waldemar Bartyna 1 Programowanie zaawansowane Zaawansowane konstrukcje języka C#"— Zapis prezentacji:

1 Waldemar Bartyna 1 Programowanie zaawansowane Zaawansowane konstrukcje języka C#

2 Waldemar Bartyna Plan wykładu 1.Niejawne typowanie zmiennych lokalnych 2.Właściwości automatyczne 3.Metody rozszerzeniowe 4.Metody częściowe 5.Inicjalizator obiektów 6.Typy anonimowe 2

3 Waldemar Bartyna 3 Niejawne typowanie zmiennych lokalnych

4 Waldemar Bartyna Jawne deklarowanie zmiennych Dotychczasowe doświadczenia z językiem C# pokazują, że zmienne (np. w ciele metody) deklarowane są w jawny sposób – jawnie podajemy typ deklarowanej zmiennej. 4

5 Waldemar Bartyna Niejawne typowanie zmiennych lokalnych C# udostępnia słowo kluczowe var, które możemy użyć zamiast jawnego podania typu zmiennej. W przypadku użycia tego słowa, kompilator wywnioskuje typ zmiennej na podstawie początkowej wartości nadanej tej zmiennej w momencie jej inicjalizacji. 5

6 Waldemar Bartyna Czy var to słowo kluczowe? Ściśle mówiąc, var nie jest słowem kluczowym języka C#. Dopuszczalne jest deklarowanie zmiennych, parametrów, pól danych o takiej nazwie bez pojawienia się jakichkolwiek ostrzeżeń w czasie kompilacji. Jednakże, gdy słowo var wystąpi jako typ zmiennej, na podstawie kontekstu traktowane jest jako słowo kluczowe. 6

7 Waldemar Bartyna Niejawne typowanie Niejawne typowanie możemy stosować w przypadku deklarowania dowolnego typu, w tym: tablic, typów generycznych, naszych własnych typów niestandardowych. 7

8 Waldemar Bartyna Niejawnie typowanie w pętli foreach Słowa kluczowego var można również używać w standardowej pętli foreach. Kompilator sam poprawnie wywnioskuje typ zmiennej, za pomocą której przechodzimy po elementach wskazanej kolekcji. W poniższym przykładzie, iterujemy po elementach niejawnie zadeklarowanej tablicy liczb całkowitych. 8

9 Waldemar Bartyna Niejawnie typowanie w pętli foreach c. d. Gwoli wyjaśnienia, w pętli foreach możemy użyć jawnie zadeklarowanego iteratora do przechodzenia po elementach niejawnie zadeklarowanej tablicy. 9

10 Waldemar Bartyna Ograniczenia niejawnego typowania Niejawnego typowania możemy używać tylko w przypadku deklarowania zmiennych lokalnych w ramach ciała metody lub właściwości. Nielegalnym jest używanie słowa kluczowego var do deklarowania typu zwracanego przez metodę, typów parametrów metod oraz typów pól danych klas lub struktur. 10

11 Waldemar Bartyna Ograniczenia niejawnego typowania c. d. Do zmiennych lokalnych deklarowanych przy użyciu słowa var musi być przypisana wartość początkowa w momencie deklaracji. Dodatkowo, wartością tą nie może być null. Wynika to z oczywistego faktu: kompilator musi mieć podstawę do wywnioskowania typu deklarowanej zmiennej. 11

12 Waldemar Bartyna Ograniczenia niejawnego typowania c. d. 2 Do już zainicjalizowanej zmiennej możemy przypisać wartość null, Zmienną niejawnie zadeklarowaną możemy przypisać do innej zmiennej (zadeklarowanej jawnie lub niejawnie), Możemy zwrócić zmienną zadeklarowaną niejawnie, o ile typ wartości zwracanej przez daną metodą jest zgodny z typem zwracanej zmiennej. 12

13 Waldemar Bartyna Ograniczenia niejawnego typowania c. d. 3 Nie można również deklarować nulowalnych, niejawnie typowanych zmiennych lokalnych. Deklarowanie niejawnie typowanych zmiennych jako nulowanych nie ma sensu, ponieważ nie można do nich przypisać wartości null w momencie inicjalizacji. 13

14 Waldemar Bartyna Niejawnie typowane lokalne tablice Stosując technikę niejawnego typowania możemy zadeklarować tablicę nie podając typu jej elementów. 14

15 Waldemar Bartyna Niejawnie typowane lokalne tablice c. d. Tak, jak w przypadku jawnego deklarowania tablicy, lista inicjalizacyjna musi zawierać elementy tego samego typu (same liczby, same napisy). W przypadku niejawnego deklarowania lokalnej tablicy o różnych elementach, wywnioskowanym typem nigdy nie stanie się klasa bazowa System.Object. Poniższa deklaracja spowoduje błąd kompilacji. 15

16 Waldemar Bartyna Niejawne typowanie a silne typowanie Niejawnie typowane dane są silnie typowanymi danymi. Technika niejawnego typowania realizowana jest w momencie deklaracji zmiennej i polega na niejawnym wywnioskowaniu i nadaniu typu deklarowanej zmiennej. Później dana zmienna jest traktowana jak każda inna. 16

17 Waldemar Bartyna Zastosowanie niejawnego typowania Samo niejawne deklarowanie lokalnych zmiennych, których typy doskonale znamy nie ma większego sensu. Takie postępowanie prowadziłoby do zmniejszenia czytelności kodu. Główne zastosowania tej techniki (jak i wielu innych) ma miejsce w technologii LINQ. Technologia ta pozwala na korzystanie z zapytań (ang. query expression), których rezultatem są dynamicznie tworzone zbiory wyników powstałe na podstawie samego formatu zapytania. Stosując niejawne deklarowanie zmiennych, nie musimy znać typu zwracanego przez te wyrażenia, co, dodatkowo, nie w każdym przypadku jest w ogóle możliwe (np. gdy używany typów anonimowych). 17

18 Waldemar Bartyna Zastosowanie niejawnego typowania c. d. 18

19 Waldemar Bartyna 19 Automatyczne właściwości

20 Waldemar Bartyna Właściwości C# preferuje stosowanie właściwości w celu zapewnienia bezpieczeństwa dostępu i przypisywania nowych wartości prywatnym polom danych (w odróżnieniu od tradycyjnie używanych metod GetXXX() i SetXXX()). 20

21 Waldemar Bartyna Właściwości automatyczne Chociaż definiowanie takich właściwości nie stanowi intelektualnej zagadki stulecia (szczególnie stosująć propfull + tab tab), zdefiniowanie ich, na przykład, dla 15 pól danych wiąże się ze sporym nakładem czasowym. Ma to szczególne zastosowanie w przypadku, gdy definiowane właściwości tylko zwracają i przypisują wartość polom prywatnym. W celu uproszczenia procesu hermetyzacji danych, C# wprowadza automatyczne właściwości. Mechanizm ten zastępuje definiowanie prywatnej zmiennej i tworzenie ciała odpowiedniej właściwości. 21

22 Waldemar Bartyna Właściwości automatyczne c. d. Podczas definiowania właściwości podajemy modyfikator dostępu, typ danych, nazwę właściwości oraz pusty blok get/set. W czasie kompilacji, do danego typu zostanie dodane odpowiednie, prywatne pole danych oraz odpowiednia implementacja logiki bloku get/set. W przypadku, gdy chcemy w metodach get i set umieścić dodatkową funkcjonalność (walidacja itd.) musimy zdefiniować tradycyjną właściwość. Gotowiec o nazwie „prop” (ang. code snippet) w środowisku Visual Studio 2013 został zastąpiony wersją obsługującą automatyczne właściwości (wcześniej obsługiwał tradycyjne właściwości). 22

23 Waldemar Bartyna Ograniczenie automatycznych właściwości W odróżnieniu od tradycyjnych, właściwości automatyczne muszą posiadać zarówno implementację metody get i metody set. Oznacza to, że nie można za ich pomocą definiować właściwości tylko do odczytu lub tylko do zapisu pomijając jeden z bloków.. 23

24 Waldemar Bartyna Odwołaniado automatycznych właściwości Programista nie ma bezpośredniego dostępu do wygenerowanego prywatnego pola danych, nawet z wnętrza typu, w którym zostało wygenerowane. Dostęp z zewnątrz do właściwości wygląda dokładnie tak samo, jak w przypadku tradycyjnych właściwości. 24

25 Waldemar Bartyna Ograniczanie dostępu W przypadku tradycyjnych właściwości możemy definiować modyfikatory dostępu osobno dla metody get i set. Tak samo możemy postąpić w przypadku właściwości automatycznych (ograniczając w ten sposób dostęp do wybranej metody z zewnątrz typu). 25

26 Waldemar Bartyna 26 Metody rozszerzeniowe

27 Waldemar Bartyna Metody rozszerzeniowe Gdy typ jest zdefiniowany i skompilowany do pakietu platformy.NET, nie możemy zmieniać sposobu, w jaki został zdefiniowany. Jedynym sposobem na dodanie nowych składowych, aktualizowanie składowych lub ich usunięcie jest modyfikacja i ponowna kompilacja kodu. W C# możliwe jest definiowanie metod rozszerzeniowych (rozszerzających). Metody rozszerzeniowe pozwalają na dodanie nowej funkcjonalności do istniejących, skompilowanych typów (klas, struktur i interfejsów) lub typów aktualnie kompilowanych, bez konieczności bezpośredniej ich zmiany. 27

28 Waldemar Bartyna Wszystko jest iluzją Technika ta jest bardzo użyteczna, gdy chcemy wstrzyknąć nową funkcjonalność do typu, dla którego nie posiadamy kodu bazowego. Możemy jej również użyć do „zmuszenia” typu do dostarczania zbioru składowych (które mogą zostać użyte w mechanizmie polimorfizmu), ale nie możemy zmodyfikować oryginalnej deklaracji typu. Używając metod rozszerzeniowych możemy dodać funkcjonalność do prekompilowanych typów, jednocześnie tworząc iluzje obecności tych metod w oryginalnych deklaracjach typów. 28

29 Waldemar Bartyna Ograniczenia metod rozszerzeniowych Metody rozszerzeniowe muszą być definiowane w statycznych klasach. Dlatego też, każda metoda rozszerzeniowa musi być zadeklarowana jako statyczna. Metody są oznaczane jako rozszerzające poprzez użycie słowa kluczowego this jako modyfikatora pierwszego (i tylko pierwszego) parametru danej metody. Każda metoda rozszerzeniowa może być wywołana z aktualnej instancji typu lub statycznie, poprzez definiującą ją klasę statyczną. 29

30 Waldemar Bartyna Definiowanie metod rozszerzeniowych Załóżmy, że chcemy zdefiniować klasę narzędziową zawierającą dwie metody rozszerzeniowe. Pierwsza z nich rozszerzy klasę System.Object o całkowicie nową metodę pozwalającą na wyświetlenie informacji o pakiecie, w którym dany typ został zdefiniowany. Druga metoda rozszerzy typ System.Int32 o możliwość odwracania kolejności cyfr w liczbie. 30

31 Waldemar Bartyna Definiowanie metod rozszerzeniowych c. d. 31

32 Waldemar Bartyna Definiowanie metod rozszerzeniowych c. d. 2 Przed definicją typu pierwszego parametru każdej z metod rozszerzeniowych znajduje się słowo kluczowe this. Pierwszy parametr metody rozszerzeniowej wskazuje na typ, który dana metoda rozszerza. Ponieważ metoda DisplayDefiningAssembly() rozszerza klasę System.Object, każdy typ w każdym z pakietów posiada teraz taką metodę. Metoda ReverseDigits() rozszerza tylko typ int. Próba wywołania jej dla innych typów spowoduje błąd kompilatora. 32

33 Waldemar Bartyna Definiowanie metod rozszerzeniowych c. d. 3 Metoda rozszerzająca może przyjmować dowolną liczbę parametrów. Jednak tylko pierwszy parametr może być poprzedzony słowem kluczowym this (wskazywać na typ rozszerzany przez daną metodę). 33

34 Waldemar Bartyna Wywołanie metod rozszerzeniowych 34

35 Waldemar Bartyna Wywołanie metod rozszerzeniowych c. d. Na poprzednim slajdzie pokazano przykłady wywołania metod rozszerzających z poziomu instancji rozszerzanych typów. „W tle” kompilator wywołuje odpowiednie metody statyczne, przekazując dany obiekt jako pierwszy parametr danej metody (ten oznaczony słowem kluczowym this w jej definicji). Nic nie stoi na przeszkodzie tego, żebyśmy sami mogli wywołać jawnie odpowiednie metody statyczne z naszych klas narzędziowych (co pokazano na następnym slajdzie). 35

36 Waldemar Bartyna Wywołanie metod rozszerzeniowych c. d. 2 36

37 Waldemar Bartyna Dostęp do rozszerzanego typu Metody rozszerzeniowe są statycznymi metodami, które można wywołać na poziomie instancji rozszerzanego typu. W odróżnieniu od „zwykłych„ metod nie mają one bezpośredniego dostępu do składowych rozszerzanego typu. Mówiąc inaczej, metody takie działają na obiektach danej klasy, a nie są jej składową. Rozważmy następujący przykład. Mamy prosty typ Car. 37

38 Waldemar Bartyna Dostęp do rozszerzanego typu c. d. Chcąc napisać metodę rozszerzającą ten typ (zmniejszającą prędkość) nie możemy tego zrobić w poniższy sposób. Do składowych publicznych rozszerzanej klasy musimy odwoływać się poprzez pierwszy parametr metody rozszerzającej. 38

39 Waldemar Bartyna Dostęp do rozszerzanego typu c. d. 2 Biorąc pod uwagę sposób, w jaki są realizowane metody rozszerzające, brak bezpośredniego dostępu do składowych rozszerzanego typu jest oczywisty. Tworzymy zewnętrzną metodę operującą na instancji danej klasy. Z drugiej jednak strony, sam sposób wywołania metody rozszerzającej z poziomu instancji wskazywałaby na istnienie takiej możliwości. 39

40 Waldemar Bartyna Widoczność metod rozszerzających Statyczne klasy z metodami rozszerzającymi definiowane są przez nas w pewnej przestrzeni nazw. Podobnie jak z typami, aby móc z nich skorzystać w innych projektach (innych przestrzeniach nazw) musimy je wskazać poprzez użycie słowa kluczowego using. Jeżeli tego nie zrobimy, obiekty w danej przestrzeni nie będą posiadać dodatkowych (zdefiniowanych przez nas w innym zakresie widoczności) metod rozszerzających. 40

41 Waldemar Bartyna System pomocy Biorąc pod uwagę fakt, że metody rozszerzające nie są definiowane dosłownie w rozszerzanym typie, korzystanie z nich podczas kodowania może być niewygodne. Korzystając z systemu pomocy, w przypadku istnienia metod rozszerzających, po wyświetleniu listy z możliwymi metodami może się okazać, że znajduje się tam wiele metod nie istniejących w oryginalnej definicji. Na szczęście, system IntelliSense wyróżnia takie metody specjalną ikonką (strzałką skierowaną do dołu). 41

42 Waldemar Bartyna Rozszerzanie interfejsów Możliwe jest również dodawanie nowej funkcjonalność do interfejsów poprzez definiowanie metod rozszerzeniowych. Powiedzmy, że mamy następującą definicję interfejsu i klasy go implementującej. 42

43 Waldemar Bartyna Rozszerzanie interfejsów c. d. Załóżmy, że nie mamy dostępu do oryginalnego kodu interfejsu. Dlatego też chcemy zdefiniować nową metodę rozszerzeniową. Definiując taką metodę, nie wystarczy zdefiniować jej prototyp. Musimy również dostarczyć implementację danej metody. Dlaczego? 43

44 Waldemar Bartyna Rozszerzanie interfejsów c. d. 2 Ze zdefiniowanej metody możemy korzystać, jak z każdej innej składowej danego interfejsu. 44

45 Waldemar Bartyna 45 Metody częściowe

46 Waldemar Bartyna Metody częściowe Od wersji.Net 2.0 mamy możliwość definiowania częściowych typów, pozwalające na rozłożenie pełnej implementacji typu na kilka plików. Dopóki wszystkie części klasy są w ten sam sposób nazywane (nazwa kwalifikowana), wynikiem ich kompilacji jest pojedyncza definicja typu w tworzonym pakiecie. C# 2008 rozszerza zastosowanie słowa kluczowego partial do stosowania na poziomie metod. 46

47 Waldemar Bartyna Ograniczenia metod częściowych Technika metod częściowych pozwala na definiowanie prototypu metody w jednym pliku, a jej implementacji w innym pliku. Metody częściowe mają wiele istotnych ograniczeń: Mogą być definiowane tylko w klasach częściowych, Musza zwracać void, Mogą być statyczne lub instancyjne, Mogą posiadać parametry (również z atrybutami this, ref lub params, ale nie z atrybutem out), Są zawsze niejawnie prywatne. 47

48 Waldemar Bartyna Definiowanie metod częściowych Przykładowa definicja klasy zawierająca deklarację i wywołanie metody częściowej. 48

49 Waldemar Bartyna Definiowanie metod częściowych c. d. Jeżeli programista nie dostarczy implementacji metody zadeklarowanej jako częściowa, w czasie kompilacji „ślad po niej zaginie” – skompilowany typ nie będzie zawierał ani deklaracji takiej metody ani jej wywołań. 49

50 Waldemar Bartyna Definiowanie metod częściowych c. d. 2 Aby wywołania metody zostały włączone do typu, należy zapewnić jej implementację w osobnym pliku stanowiącym dalszą część definicji danej klasy. 50

51 Waldemar Bartyna Zastosowanie metod częściowych Ze wszystkich nowości C# 2008, metody częściowe będą zapewne najmniej wykorzystywane ze względu na ich ograniczenia (przede wszystkim są zawsze prywatne i muszą zwracać void). Najczęstszym zastosowaniem tej techniki jest tzw. lekka obsługa zdarzeń Programista, poprzez definiowanie metody częściowej, zapewnia uchwyt do obsługi pewnych zdarzeń. Programiści korzystający z danego typu mogą zapewnić odpowiednią obsługę zdarzenia implementując metodę częściową. 51

52 Waldemar Bartyna 52 Inicjalizator obiektów

53 Waldemar Bartyna Inicjalizator obiektów C# 2008 oferuje nowy sposób nadawania początkowych wartości polom klasy poprzez tak zwaną składnię inicjalizatora obiektów. Korzystając z tej techniki możliwe jest stworzenie nowej zmiennej danego typu i przypisanie wartości jej właściwościom i publicznym polom w kilku liniach kodu. Syntaktycznie, inicjalizator obiektów składa się z listy wartości oddzielonych przecinkiem, zamkniętych w nawiasach klamrowych. Każda składowa w liście inicjalizacyjnej reprezentuje odpowiednie publiczne pole lub właściwość inicjalizowanego typu. 53

54 Waldemar Bartyna Kod przykładowej klasy 54

55 Waldemar Bartyna Użycie inicjalizatora Dzięki inicjalizatorowi obiektów mamy kolejny sposób na utworzenie obiektu danego typu. 55

56 Waldemar Bartyna Użycie inicjalizatora c. d. Za pomocą składni inicjalizatora można nadawać wartości właściwościom i polom publicznym typu. Jakie wartości mają pola xPos i yPos po następującym zainicjalizowaniu obiektu? Powyższy zapis odpowiada następującemu: 56

57 Waldemar Bartyna Jawne wywołanie konstruktorów Inicjalizator może być również wywoływany po jawnym wskazaniu konstruktora. Wywołanym konstruktorem nie musi być wyłącznie konstruktor domyślny (nie posiadający żadnych argumentów). 57

58 Waldemar Bartyna Inicjowanie kolekcji Za pomocą składni inicjalizatora możemy również inicjować kolekcje. 58

59 Waldemar Bartyna Przydatność inicjalizatora Użyteczność składni inicjaliztora obiektów można łatwo zauważyć przy inicjalizowaniu bardziej złożonych obiektów. 59

60 Waldemar Bartyna 60 Typy anonimowe

61 Waldemar Bartyna Typy anonimowe Tworząc nową klasę musimy (w większości przypadków) zaimplementować następujące jej elementy przedstawione poniżej. Jeżeli chcemy stworzyć tymczasową klasę bez konieczności implementowania powyższego standardowego szkieletu możemy skorzystać z konstrukcji języka C# 2008 jakimi są typy anonimowe. 61

62 Waldemar Bartyna Definiowanie typu anonimowego Typ anonimowy tworzymy używając słowa var, słowa kluczowego new i podając listę właściwości i ich wartości w nawiasach klamrowych. 62

63 Waldemar Bartyna Wewnętrzna reprezentacja typu anonimowego 63

64 Waldemar Bartyna Cechy typu anonimowego Nie kontrolujemy nazwy typu, Typy anonimowe zawsze dziedziczą po System.Object. Właściwości tylko do odczytu Typy anonimowe nie wspierają metod, zdarzeń. Typy anonimowe są zawsze niejawnie zamknięte. Typy anonimowe są zawsze tworzone za pomocą domyślnego konstruktora. Przesłonięta metoda ToString() wyświetla właściwości typu w standardowy sposób. Przesłonięte metody Equals() i GetHashCode() operują na wartościach a nie na referencjach. 64

65 Waldemar Bartyna 65 Dziękuję za uwagę

66 Waldemar Bartyna Pytania egzaminacyjne (Zestaw01) 1.Co to jest takiego ta cała platforma.NET? 2.Jakie są jej najważniejsze cechy? 3.Jakie elementy składają się na platformę.NET? 4.Co to jest CLR? 5.Co to jest CTS? 6.Co to jest CLS? 7.Co to jest CIL? 8.Co to jest CLI? 66

67 Waldemar Bartyna Pytania egzaminacyjne (Zestaw02) 9.Co to są pakiety? 10.Co to są typy i co do nich zaliczamy? 11.Jaka jest zależność między pakietami, przestrzeniami nazw i typami? 12.W jaki sposób wykonywane są programy na platformie.NET? 67

68 Waldemar Bartyna Pytania egzaminacyjne (Zestaw03) 13.Na czym polega OOP i jakie są jego trzy podstawowe filary? 14.Na czym polega dziedziczenie? 15.Do czego wykorzystujemy słowa kluczowe protected, base, sealed? 16.Na czym polega agregacja? 17.Na czym polega zagnieżdżanie? 18.Na czym polega delegacja? 68

69 Waldemar Bartyna Pytania egzaminacyjne (Zestaw04) 19.Na czym polega polimorfizm? 20.Co to są abstrakcyjne klasy bazowe? 21.Co to jest interfejs polimorficzny? 22.Na czym polega nadpisywanie i zasłanianie składowych? 69

70 Waldemar Bartyna Pytania egzaminacyjne (Zestaw05) 23.Czym charakteryzuje się klasa System.Object? 24.Na czym polega obsługa wyjątków? 25.Jakie są bloki i słowa kluczowe związane z obsługą wyjątków? 26.Jak realizujemy przechwytywanie wielu wyjątków? 70

71 Waldemar Bartyna Pytania egzaminacyjne (Zestaw06) 27.Co to jest interfejs? 28.Czym interfejs różni się od interfejsu polimorficznego? 29.W jaki sposób implementujemy interfejsy? 30.Do czego służą słowa kluczowe as i is? 31.Czym się różni jawna i niejawna implementacja interfejsów? 32.Co trzeba zrobić, aby po zawartości naszej klasy można było iterować za pomocą pętli foreach 33.Czym się różnie płytkie i głębokie klonowanie? 34.Co trzeba zrobić, aby kolekcję naszych obiektów można było posortować? 71

72 Waldemar Bartyna Pytania egzaminacyjne (Zestaw07) 35.Co to są typy generyczne? 36.Na czym polega o/wypakowywanie i gdzie jest wykorzystywane? 37.Wymień znane Ci kolekcje generyczne. 38.Co to są indeksery? 39.Na jakiej zasadzie działają metody generyczne? 72

73 Waldemar Bartyna Pytania egzaminacyjne (Zestaw08) 40.Co to są delegaty? 41.Co to są zdarzenia? 42.Co to są metody anonimowe? 43.Co to są wyrażenia lambda? 44.Na czym polega multicasting? 45.Na czym polega kowariancja i kontrkowariancja? 46.Co to są predykaty? 73

74 Waldemar Bartyna Pytania egzaminacyjne (Zestaw09) 47.Na czym polega niejawne typowanie typów? 48.Co to są właściwości anonimowe? 49.Co to są metody rozszerzeniowe? 50.Co to są klasy i metody częściowe? 51.W jaki sposób wykorzystujemy inicjalizator obiektów? 52.Co to są typy anonimowe? 74


Pobierz ppt "Waldemar Bartyna 1 Programowanie zaawansowane Zaawansowane konstrukcje języka C#"

Podobne prezentacje


Reklamy Google