7. Metody – Grupa poleceń, która wykonuje określone działanie
7. Metody – Grupa poleceń, która wykonuje określone działanie
Punkty w nagraniu:
- Czym jest metoda?
- Struktura metody
- Metoda nie zwracająca wartość
- Metoda zwracająca wartości
- Rekurencyjne wywołanie metody
- Metoda z parametrami
- Parametry typu wartościowego
- Parametry typu referencyjnego
- Parametry typu wyjściowego
- Metoda z parametrami domyślnymi
- Przykłady z poziomu kodu
Linki:
- Projekt do pobrania: Link
1. Czym jest metoda?
Metoda jest zbiorem konkretnych kroków, które muszą być zrealizowane. Dla przykładu z lewej strony mamy przepis na naleśniki. Mamy wymienione składniki wraz z ich ilością, pod spodem jest lista kroków do zrealizowania. Tak samo jest w przypadku metod w programowaniu, mamy metodę, które wykonuje konkretne zadanie, może mieć zmiennej lokalne z konkretnymi wartościami jak i konkretne instrukcje do zrealizowania.
2. Struktura metody
Tworzenie metody zaczynamy od modyfikatora dostępu (opcjonalnie), odpowiada on za to czy dostęp do metody będzie możliwy w konkretnym miejscu. Potem podajmy typ zwracanej wartości, nazwę metody oraz listę parametrów o ile takowe metoda będzie posiadała.
- modyfikator dostępu – określa widoczność zmiennej lub metody. Gdy nie podamy żadnego modyfikatora to kompilator uzna to jako private. Więcej o modyfikatorach dostępu omówię w jednym z przyszłych materiałów.
- zwracany typ – określamy czy metoda ma zwracać jakąś wartość czy raczej ma tylko wykonać kod w niej zawarty. Zatem jeśli metoda nie zwraca żadnej wartości to piszemy void, jeśli metoda ma nam zwrócić ciąg znaków (tekst) to podajemy string itd.
- nazwa metody – przy tworzeniu nazwy metody korzystamy ze stylu nazewnictwa Pascal’a, czyli zaczynamy z dużej litery i jeśli metoda w nazwie ma więcej niż jedno słowo to każde kolejne słowo zaczynamy z dużej litery. Nazwa metody nie może zaczynać się od cyfry i znaków specjalnych takich jak ,.;@#
- lista parametrów – metoda domyślnie nie musi posiadać parametrów, ale nic nie stoi na przeszkodzie żeby je dodać, parametry dodajemy zaczynając od podania typu, a później nazwy zaczynając od małej litery i tak samo jak w przypadku nazwy metody, jeśli nazwa parametru składa się z więcej niż jednego słowa to każde kolejne słowo zaczynamy od dużej litery.
3. Metoda nie zwracająca wartości
void Addition() { int a = 5; int b = 10; Console.WriteLine($"{a} + {b} = {a + b}"); } // Wyświetli 5 + 10 = 15
W podanym przykładzie, metoda nie zwraca żadnej wartości (typ void). Mamy zadeklarowane i zainicjalizowane dwie zmienne, które są wyświetlane w konsoli
void GetAndDisplayNicknameAndLevel() { Console.Write("Podaj ksywkę: "); string name = Console.ReadLine(); Console.Write("Podaj poziom: "); string level = Console.ReadLine(); Console.WriteLine($"Witaj {name} ({level})!"); } // Wyświetli Podaj ksywkę: Tajko Podaj poziom: 100 Witaj Tajko (100)!
W tej metodzie wyświetlamy komunikat w konsoli oraz pobieramy tekst z klawiatury po czym wyświetlamy to w konsoli.
4. Metoda zwracająca wartość
string nickname = GetNickname(); Console.WriteLine($"Twoja ksywka: {nickname}"); string GetNickname() { Console.Write("Podaj ksywkę: "); return Console.ReadLine(); } // Wyświetli Podaj ksywkę: Tajko Twoja ksywka: Tajko
W odróżnieniu do poprzedniej metody, metoda GetNickname() zwraca ciąg znaków, który przypisujemy do zmiennej nickname po czym wyświetlamy w konsoli. Więc nic nie stoi na przeszkodzie żeby nasza metoda zwracała ciąg znaków, liczbę całkowitą jak i zmiennoprzecinkową itp.
string _nickname = "Tajko"; string GetNicknameWithDatetime() => $"{_nickname} ({DateTime.Now})"; Console.WriteLine($"Ksywka z datą: {GetNicknameWithDatetime()}"); // Wyświetli Ksywka z datą: Tajko (11.06.2022 19:06:11)
Ponownie tworzymy metodę zwracającą ciąg znaków tylko w tym przypadku korzystamy ze zmiennej, którą wcześniej zadeklarowaliśmy oraz wyświetlamy dodatkowo datę
5. Rekurencyjne wywołanie metody
using System.Diagnostics; Stopwatch sw = new Stopwatch(); sw.Start(); Console.WriteLine($"Silnia rekurencyjnie dla liczby 8: {ObliczSilnieRekurencyjnie(8)} - czas: {sw.ElapsedTicks}"); sw.Stop(); long ObliczSilnieRekurencyjnie(int liczba) { if (liczba <= 1) return 1; return ObliczSilnieRekurencyjnie(liczba - 1) * liczba; } // Wyświetli Silnia rekurencyjnie dla liczby 8: 40320 - czas: 1573
Metoda rekurencyjna to taka metoda, która wywołuje samą siebie. W podanym przykładzie obliczamy silnię rekurencyjnie dla liczby 8. Dodatkowo korzystamy z klasy Stopwatch, która odpowiada za zmierzenie pomiaru czasu, który był potrzebny na wykonanie danej operacji, w naszym przypadku sprawdzi ile czasu było potrzeba na wykonanie obliczenia silni dla liczby 8
6. Wydajność między wywołaniem rekurencyjnym, a iteracyjnym
using System.Diagnostics; Stopwatch sw = new Stopwatch(); sw.Start(); Console.WriteLine($"Silnia iteracyjnie dla liczby 8: {ObliczSilnieIteracyjnie(8)} - czas: {sw.ElapsedTicks}"); sw.Stop(); sw.Restart(); sw.Start(); Console.WriteLine($"Silnia rekurencyjnie dla liczby 8: {ObliczSilnieRekurencyjnie(8)} - czas: {sw.ElapsedTicks}"); sw.Stop(); Console.ReadKey(); long ObliczSilnieIteracyjnie(int liczba) { if (liczba <= 1) return 1; long res = 1; for (int i = 1; i <= liczba; i++) res *= i; return res; } long ObliczSilnieRekurencyjnie(int liczba) { if (liczba <= 1) return 1; return ObliczSilnieRekurencyjnie(liczba - 1) * liczba; } // Wyświetli Silnia iteracyjnie dla liczby 8: 40320 - czas: 2053 Silnia rekurencyjnie dla liczby 8: 40320 - czas: 593
Nie wiem czy to wina mojego kompa czy co, ale podczas testów, sprawdziłem jaka jest prędkość obliczenia z identycznym kodem w projekcie .NET Framework 4.8 oraz .NET 6.
W .NET Framework 4.8 obliczenie iteracyjne było o wiele szybsze w porównaniu do rekurencyjnego, a w .NET 6 na odwrót, rekurencyjne było szybsze, a iteracyjne było wolniejsze. Możecie sprawdzić na waszych komputerach jak to będzie wyglądało. Sprawdziłem jeszcze w internetach i stety/niestety trafiałem jedynie na testy przeprowadzane w starszych wersjach .NET Framework i u każdego rozwiązanie rekurencyjne było wolniejsze, więc pewnie jest coś na rzeczy i być może w nowszej wersji .NET poprawili to.
7. Metody z parametrami
1. Parametry typu wartościowego
Metody z parametrami wartościowymi są prawie najczęściej spotykanymi metodami. Jak łatwo można się domyślić, są to metody do których w parametrach przekazujemy zmienne wartościowe np. takie jak int. Przy takich parametrach zmiany wewnątrz metody, które korzystają z wartości przekazanych w parametrze, zostaję w metodzie, więc jeśli chcemy dla przykłady, zamienić miejscami wartość dwóch zmiennych, to nam się nie uda, ponieważ zmiana nastąpi tylko wewnątrz metody. Ale nic nie stoi na przeszkodzie żeby wykorzystać przekazane parametry do różnych operacji wewnątrz metody
Addition(5, 10); void Addition(int a, int b) { Console.WriteLine($"{a} + {b} = {a + b}"); } // Wyświetli 5 + 10 = 15
Metoda nie zwracająca wartości, przyjmuje dwa parametry typu liczby całkowitej int, która ma za zadanie wyświetlić operację dodawania.
int noA = 5, noB = 10; Console.WriteLine($"a = {noA}, b = {noB}"); ChangePosition(noA, noB); Console.WriteLine($"a = {noA}, b = {noB}"); void ChangePosition(int a, int b) { int temp = a; a = b; b = temp; } // Wyświetli a = 5, b = 10; a = 5, b = 10;
Metoda ChangePosition miała za zadanie zamienić wartości zmiennych przekazanych do metody, czyli jeśli noA = 5, noB = 10, to po zamianie noA powinien mieć wartość 10, a noB = 5. Tak się niestety nie stało, cała zmiana została wykonana jedynie wewnątrz metody.
Dzieje się dlatego, ponieważ w momencie wywołania metody, tworzona jest nowa komórka pamięci dla każdego parametru i co za tym idzie wartości przekazanych parametrów są przekazywane do nowo utworzonych komórek pamięci i znikają, gdy wywołanie metody się zakończy. Co za tym idzie, zmiany dokonane wewnątrz metody nie mają wpływu na wartość argumentów przekazanych do metody.
2. Metoda z parametrami typu referencyjnego
Czym jest parametr referencyjny? jest to odniesienie (referencja) do adresu w pamięci wskazanego parametru. Gdy przekazujemy parametr przez referencję, nie jest tworzona nowa komórka pamięci tak jak to było w przypadku parametru wartościowego co za tym idzie, gdy wprowadzimy zmiany wewnątrz metody akurat na tym parametrze to zmiany będą widoczne na zewnątrz metody. Więc parametr referencyjny ma to samo odniesienie do miejsca w pamięci co używany parametr.
int noA = 5, noB = 10; Console.WriteLine($"a = {noA}, b = {noB}"); ChangePosition(ref noA, ref noB); Console.WriteLine($"a = {noA}, b = {noB}"); void ChangePosition(ref int a, ref int b) { int temp = a; a = b; b = temp; } // Wyświetli a = 5, b = 10; a = 10, b = 5;
Przy metodzie z parametrami typu referencyjnego, przed podaniem typu parametru dopisujemy słowo kluczowe ref. Podobnie przy wywołaniu metody. Teraz problem podmiany wartości dla przekazanych zmiennych w parametrze zostanie rozwiązany. Dzięki temu nie tworzymy nowego miejsca w pamięci, a korzystamy z tego samego
Player player = new Player("Tajko", 100); Console.WriteLine($"Ksywka = {newPlayer.Nickname}, Poziom = {newPlayer.Level}"); ReplacePlayer(player, "Goku", 999); Console.WriteLine($"Ksywka = {newPlayer.Nickname}, Poziom = {newPlayer.Level}"); void ReplacePlayer(Player player, string nickName, int level) { player.Nickname = nickname; player.Level = level; } // Wyświetli Ksywka = Tajko, Poziom = 100 Ksywka = Goku, Poziom = 999
W powyższym przykładzie w parametrze metody przekazujemy klasę, klasy same w sobie są typem referencyjnym. Więc teoretycznie i praktycznie powinno zaskoczyć i zaskakuje, przekazaliśmy playera i ustawiliśmy dwie nowe wartości, które później po podglądzie były widoczne.
Player oldPlayer = new Player("Tajko", 100); Player newPlayer = new Player("", 0); Console.WriteLine($"Ksywka = {newPlayer.Nickname}, Poziom = {newPlayer.Level}"); ReplacePlayer(oldPlayer, newPlayer); Console.WriteLine($"Ksywka = {newPlayer.Nickname}, Poziom = {newPlayer.Level}"); void ReplacePlayer(Player oldPlayer, Player newPlayer) { newPlayer = oldPlayer; } // Wyświetli Ksywka = , Poziom = 0 Ksywka = , Poziom =
Teraz chcieliśmy żeby newPlayer miał takie same wartości co oldPlayer, więc utworzono metodę ReplacePlayer w której przypisano do nowego playera, starego playera. Teoretycznie powinno działać skoro oba parametry są typem referencyjnym, ale niestety tak się nie stało. Wewnątrz metody, owszem, wskazano na ten samo obiekt, ale niestety zmiana była widoczna tylko wewnątrz metody. Żeby rozwiązać ten problem wystarczy dodać słowo kluczowe ref tak jak to było przy parametrach typu referencyjnego.
Player oldPlayer = new Player("Tajko", 100); Player newPlayer = new Player("", 0); Console.WriteLine($"Ksywka = {newPlayer.Nickname}, Poziom = {newPlayer.Level}"); ReplacePlayer(oldPlayer, ref newPlayer); Console.WriteLine($"Ksywka = {newPlayer.Nickname}, Poziom = {newPlayer.Level}"); void ReplacePlayer(Player oldPlayer, ref Player newPlayer) { newPlayer = oldPlayer; } // Wyświetli Ksywka = , Poziom = 0 Ksywka = Tajko, Poziom = 100
W powyższym przykładzie rozwiązujemy poprzedni problem i teraz przy przypisaniu oldPlayer do newPlayer, wartości zostały przepisane.
3. Metoda z parametrami typu wyjściowego
Dzięki parametrowi typu wyjściowego możemy z metody zwrócić więcej niż jedną wartość. Dla przykładu mamy metodę która zwraca liczbę całkowitą (int) i dodatkowo chcemy jeszcze zwrócić dwie kolejne wartości to nie możemy stworzyć metody w stylu int int int MyMethod() żeby zwracała nam trzy wartości. Na pomoc przychodzi parametr ze słowem kluczowym out. Warto dodać, że zmienne przekazywane do metody jako parametry typu out, nie muszą być zainicjalizowane, czyli nie muszą mieć wartości początkowej.
int randomA, randomB, randomC; randomC = GetRandomValues(out randomA, out randomB); Console.WriteLine($"Randome liczby:\nA: {randomA}\nB: {randomB}\nC: {randomC}"); int GetRandomValues(out int randomA, out int randomB) { Random rand = new Random(); randomA = rand.Next(); randomB = rand.Next(); return rand.Next(); } // Wyświetli Randomowe liczby: A: 1449500412 B: 1442671003 C: 255635915
Korzystamy z metody zwracającej liczbę całkowitą, która dodatkowo ma dwa parametry wyjściowe oznaczone słowem kluczowym out.
Na początek zadeklarowano trzy zmienne typu int, potem przypisano wynik metody GetRandomValues do zmiennej randomC i dodatkowo przypisano dwie wartości z parametrów do zmiennych randomA, randomB. Dzięki czemu przy pomocy jednej metody, zwróciliśmy trzy wartości zamiast standardowej jednej. Parametry wyjściowe są podobne do parametrów referencyjnych z taką różnicą, że parametry wyjściowe zwracają dane z metody, a referencyjne przekazują do metody.
8. Metoda z parametrami domyślnymi
Jak sama nazwa wskazuje, w metodzie można ustawić parametry, które będą miały ustawioną wartość domyślną, dzięki czemu programista nie będzie musiał ustawiać konkretnej wartości.
ShowNickname(); ShowNickname("Hinata", false); void ShowNickname(string nickname = "Tajko", bool inNewLine = false) { Console.WriteLine(inNewLine ? $"Twoja ksywka to:\n{nickname}" : $"Twoja ksywka to: {nickname}"); } // Wyświetli Twoja ksywka to: Tajko Twoja ksywka to: Hinata
W powyższym przykładzie mamy metodę, która ma ustawione dwa parametry wartościowe ustawione z wartością domyślną. Najpierw wywołujemy metodę bez ustawiania wartości w parametrach, po czym ustawiamy wartość, która nas interesuje.
ShowNickname("Tajko"); void ShowNickname(string nickname, bool inNewLine = false) { Console.WriteLine(inNewLine ? $"Twoja ksywka to:\n{nickname}" : $"Twoja ksywka to: {nickname}"); } // Wyświetli Twoja ksywka to: Tajko
Podczas wywołania powyższej metody trzeba zawsze podać wartość dla pierwszego parametry, gdy drugi jest opcjonalny, więc można, ale nie trzeba ustawiać wartości
ShowNickname("Tajko"); void ShowNickname(string nickname = "Tajko, bool inNewLine) { Console.WriteLine(inNewLine ? $"Twoja ksywka to:\n{nickname}" : $"Twoja ksywka to: {nickname}"); }
Powyższy przykład nam nie zadziała, ponieważ jeśli chodzi o parametry domyślne, muszą one być zawsze na końcu, czyli najpierw pierwszeństwo mają parametry wymagane, dopiero potem można dodać parametry domyślne, nie na odwrót.
Podsumowując, jeśli chcesz przekazać do metody parametry wartościowe i do tego chcesz żeby wprowadzone wewnątrz metody zmiany były widoczne na zewnątrz, korzystaj z parametrów referencyjnych (słowo kluczowe ref), natomiast jeśli chcesz żeby metoda zwróciła jakieś wartości, nawet jeśli jest metodą nie zwracającą wartości, czyli jest metodą typu void, to korzystaj z parametru wyjściowego out.