Parametry, przeciążanie i przekazywanie danych
Czy metoda może zmienić zmienną z Main? Odpowiedź brzmi „normalnie nie, ale da się to wymusić” — i właśnie od tego zależy, dlaczego int.TryParse wygląda tak, jak wygląda.
Czego się dziś nauczysz
- Wyjaśnisz, dlaczego metoda pracuje na kopii przekazanej wartości
- Użyjesz
ref,outiinwe właściwych sytuacjach - Nadasz parametrom wartości domyślne i wywołasz metodę argumentem nazwanym
- Napiszesz przeciążenia metody i wyjaśnisz, czym mogą się różnić
- Powiesz, gdzie sięga zmienna zadeklarowana w metodzie
Przygotowanie: lekcje 01–19. Przewidywany czas: 45–90 minut z zadaniami. Przykłady wymagają .NET 8 lub nowszego, z włączonymi ImplicitUsings i Nullable.
Metoda dostaje kopię, nie zmienną
Parametr metody to osobna zmienna, która przy wywołaniu dostaje kopię przekazanej wartości. Zmiana parametru w środku metody nie dotyka zmiennej z miejsca wywołania.
static void Wyzeruj(int liczba)
{
liczba = 0; // zmienia TYLKO lokalna kopie
Console.WriteLine($"w metodzie: {liczba}");
}
static void Main()
{
int punkty = 18;
Wyzeruj(punkty);
Console.WriteLine($"w Main: {punkty}");
}
w metodzie: 0
w Main: 18
To samo dotyczy napisów, znaków, liczb rzeczywistych i wartości logicznych — czyli wszystkiego, co poznałeś do tej pory.
Jak więc oddać wynik na zewnątrz
Normalną drogą jest return: metoda liczy i zwraca wartość, a wywołujący przypisuje ją do zmiennej. To najczytelniejszy sposób i pierwszy, po który sięgasz. Dwa słowa opisane niżej — ref i out — są wyjątkami na sytuacje, w których jeden zwracany wynik nie wystarcza.
ref, out i in — kiedy metoda sięga do zmiennej
ref — praca na tej samej zmiennej
Słowo ref mówi: „nie kopiuj, daj dostęp do oryginału”. Piszemy je dwa razy — w deklaracji metody i przy wywołaniu.
static void Zwieksz(ref int liczba)
{
liczba = liczba + 1;
}
static void Main()
{
int licznik = 5;
Zwieksz(ref licznik);
Console.WriteLine(licznik); // 6
}
Zmienna przekazywana przez ref musi mieć wartość przed wywołaniem — metoda ma prawo ją odczytać.
out — metoda ma coś zapisać
out działa podobnie, ale w jedną stronę: metoda musi przypisać wartość do takiego parametru, zanim się zakończy. Zmienna przed wywołaniem może być pusta.
static void PodzielZReszta(int a, int b, out int calosc, out int reszta)
{
calosc = a / b;
reszta = a % b;
}
static void Main()
{
PodzielZReszta(17, 5, out int c, out int r);
Console.WriteLine($"{c} reszty {r}"); // 3 reszty 2
}
Znasz to z lekcji 11: int.TryParse(tekst, out int liczba) zwraca bool — czy się udało — a samą liczbę oddaje przez out. Teraz wiesz, dlaczego tak wygląda.
in — tylko do odczytu
static double Pole(in double bok)
{
// bok = 5; // BLAD: parametru in nie wolno zmieniac
return bok * bok;
}
| Słowo | Przed wywołaniem | W metodzie | Typowe użycie |
|---|---|---|---|
| (brak) | musi mieć wartość | zmiany lokalne, niewidoczne na zewnątrz | domyślny wybór |
ref | musi mieć wartość | odczyt i zapis oryginału | zamiana dwóch zmiennych miejscami |
out | może być pusta | trzeba przypisać wartość | drugi wynik obok bool, jak w TryParse |
in | musi mieć wartość | tylko odczyt | duże struktury, gdy kopiowanie kosztuje |
Nie nadużywaj ref i out
Metoda z trzema parametrami out jest trudniejsza do czytania niż metoda zwracająca jedną sensowną wartość. Gdy potrzebujesz kilku wyników naraz, to zwykle znak, że powinny trafić do jednego obiektu — a klasy poznasz w lekcji 37.
Parametry opcjonalne i argumenty nazwane
static void Zamow(string produkt, int sztuki = 1, bool pilne = false)
{
Console.WriteLine($"{produkt} x{sztuki}, pilne: {pilne}");
}
static void Main()
{
Zamow("mysz"); // mysz x1, pilne: False
Zamow("klawiatura", 3); // klawiatura x3, pilne: False
Zamow("monitor", 2, true); // monitor x2, pilne: True
Zamow("kabel", pilne: true); // kabel x1, pilne: True
}
- Parametry z wartością domyślną muszą stać na końcu listy — inaczej kompilator nie wiedziałby, który argument pominięto.
- Wartość domyślna musi być stałą znaną przy kompilacji: liczba, napis,
true,null. - Argument nazwany (
pilne: true) pozwala pominąć środkowy parametr i przy okazji czyni wywołanie czytelnym. PorównajZamow("monitor", 2, true)zZamow("monitor", sztuki: 2, pilne: true).
Ta sama nazwa, różne listy parametrów
Przeciążenie to kilka metod o tej samej nazwie, różniących się liczbą albo typami parametrów. Kompilator wybiera właściwą po argumentach wywołania.
static int Pole(int bok) => bok * bok;
static int Pole(int szerokosc, int dlugosc) => szerokosc * dlugosc;
static double Pole(double promien) => 3.14159 * promien * promien;
Console.WriteLine(Pole(3)); // 9 - kwadrat
Console.WriteLine(Pole(3, 4)); // 12 - prostokat
Console.WriteLine(Pole(2.0)); // 12,56636 - kolo
Zapis => to skrócona postać metody składającej się z jednej instrukcji return. Znaczy dokładnie to samo co { return bok * bok; }.
Sam typ wyniku nie wystarczy
Dwie metody int Licz(int x) i double Licz(int x) to błąd CS0111. Kompilator wybiera przeciążenie po argumentach, a nie po tym, do czego przypiszesz wynik — przy takiej parze nie miałby jak zdecydować.
Przeciążanie znasz z praktyki: Console.WriteLine przyjmuje liczbę, napis, znak i wartość logiczną, bo istnieje kilkanaście jego przeciążeń.
| Para metod | Poprawne przeciążenie? |
|---|---|
Pole(int) i Pole(int, int) | tak — różna liczba parametrów |
Pole(int) i Pole(double) | tak — różne typy |
int Pole(int) i double Pole(int) | nie — CS0111, różni je tylko typ wyniku |
Pole(int a) i Pole(int b) | nie — nazwy parametrów nie mają znaczenia |
Pole(int) i Pole(int, int b = 1) | kompiluje się, ale Pole(5) pasuje do obu — unikaj |
Gdzie sięga zmienna
Zmienna zadeklarowana w metodzie istnieje tylko w niej i znika po jej zakończeniu. Dwie metody mogą mieć zmienne o tej samej nazwie — to dwie różne zmienne.
static void Pierwsza()
{
int licznik = 1;
Console.WriteLine(licznik);
}
static void Druga()
{
int licznik = 100; // inna zmienna, ta sama nazwa - to w porzadku
Console.WriteLine(licznik);
// Console.WriteLine(punkty); // BLAD: 'punkty' nie istnieje w tej metodzie
}
static void Main()
{
int punkty = 5;
Pierwsza();
Druga();
}
Dlaczego to dobrze
Gdyby metody widziały nawzajem swoje zmienne, zmiana nazwy w jednej mogłaby zepsuć drugą. Zamknięcie zmiennych w metodzie sprawia, że każdą można czytać i poprawiać osobno — po to właśnie dzielimy program na metody.
Przykład: kalkulator zamówień
class Program
{
static double Netto(double cena, int sztuki = 1)
{
return cena * sztuki;
}
static double Netto(double cena, int sztuki, double rabatProcent)
{
return Netto(cena, sztuki) * (1 - rabatProcent / 100);
}
static void ZBrutto(double netto, out double vat, out double brutto)
{
vat = netto * 0.23;
brutto = netto + vat;
}
static void Zamien(ref double a, ref double b)
{
double pomoc = a;
a = b;
b = pomoc;
}
static void Main()
{
double bez = Netto(120.0, 3);
double zRabatem = Netto(120.0, 3, 10);
Console.WriteLine($"bez rabatu : {bez:F2}");
Console.WriteLine($"z rabatem : {zRabatem:F2}");
ZBrutto(zRabatem, out double vat, out double brutto);
Console.WriteLine($"VAT : {vat:F2}");
Console.WriteLine($"brutto : {brutto:F2}");
double tania = 10, droga = 990;
Zamien(ref tania, ref droga);
Console.WriteLine($"po zamianie: {tania:F2} i {droga:F2}");
}
}
bez rabatu : 360,00
z rabatem : 324,00
VAT : 74,52
brutto : 398,52
po zamianie: 990,00 i 10,00
Co dzieje się po kolei
- Dwa przeciążenia
Nettoróżnią się liczbą parametrów. Drugie wywołuje pierwsze zamiast powtarzać mnożenie — poprawka wzoru wystarczy w jednym miejscu. Netto(120.0, 3)korzysta z jawnie podanej liczby sztuk;Netto(120.0)użyłoby wartości domyślnej 1.ZBruttooddaje dwa wyniki — dlategoout, a niereturn. Zapisout double vatdeklaruje zmienną wprost w wywołaniu.Zamiento podręcznikowe zastosowanieref: bez niego zamiana zaszłaby tylko w lokalnych kopiach i nic nie byłoby widać.
Zestawienie elementów
| Element | Znaczenie | Ograniczenia |
|---|---|---|
ref | dostęp do zmiennej wywołującego | Słowo piszemy w deklaracji i przy wywołaniu; zmienna musi mieć wcześniej wartość. |
out | wynik oddawany przez parametr | Metoda musi przypisać wartość przed każdym return. |
in | przekazanie bez prawa zmiany | Wewnątrz metody parametru nie wolno modyfikować. |
int x = 1 w liście parametrów | wartość domyślna | Tylko na końcu listy; musi być stałą znaną przy kompilacji. |
nazwa: wartość | argument nazwany | Pozwala pominąć parametry opcjonalne i uczytelnić wywołanie. |
| przeciążenie | kilka metod o tej samej nazwie | Muszą różnić się liczbą albo typami parametrów; sam typ wyniku nie wystarczy (CS0111). |
=> wyrażenie | skrócony zapis metody | Dla ciała złożonego z jednej instrukcji. Równoważne { return …; }. |
int.TryParse(t, out x) | bool | Przykład z biblioteki: wynik logiczny plus wartość przez out. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
Zwieksz(licznik) przy ref int | CS1620 — słowo ref trzeba powtórzyć przy wywołaniu. |
ref na zmiennej bez wartości | CS0165 — zmienna musi być przypisana przed wywołaniem. |
out bez przypisania w metodzie | CS0177 — parametr out musi dostać wartość na każdej ścieżce. |
| Parametr domyślny nie na końcu | CS1737 — po parametrze opcjonalnym nie może stać wymagany. |
| Przeciążenie różniące się tylko wynikiem | CS0111 — kompilator nie ma po czym wybrać. |
| Oczekiwanie, że metoda zmieni zwykły parametr | Bez ref metoda pracuje na kopii. Zwróć wynik przez return. |
Co dalej z metodami
Metoda może wywołać nie tylko inną metodę, ale również samą siebie. Ten sposób rozwiązywania zadań poznasz w lekcji 21.
Zadania
Napisz metodę void Wyzeruj(int liczba) i wywołaj ją dla zmiennej o wartości 42. Wypisz zmienną przed i po wywołaniu, a potem dopisz ref w obu miejscach i porównaj wyniki.
Napisz przeciążenia Pole(int bok) oraz Pole(int szerokosc, int dlugosc). Sprawdź: Pole(3) daje 9, a Pole(3, 4) — 12. Dodaj trzecie przeciążenie dla koła i wyjaśnij, dlaczego nie koliduje z pozostałymi.
Napisz Zamien(ref string a, ref string b) i sprawdź ją na dwóch imionach. Następnie usuń oba słowa ref, uruchom ponownie i zapisz, co się zmieniło.
Napisz void PodzielZReszta(int a, int b, out int calosc, out int reszta). Sprawdź dla 17 i 5 oraz dla 4 i 9. Zastanów się, co powinno się stać dla b równego zero — rozwiązanie poznasz w lekcji 35.
Napisz Zamow(string produkt, int sztuki = 1, bool pilne = false), wypisującą podsumowanie. Wywołaj ją czterokrotnie: z samą nazwą, z liczbą sztuk, ze wszystkimi argumentami i z pominięciem środkowego przy użyciu argumentu nazwanego.
Napisz komplet metod: double Srednia(double a, double b) i przeciążenie dla trzech ocen, string Slownie(double srednia) zwracającą opis, oraz void Podsumuj(double srednia, out double doPiatki, out bool zdane). Ułóż z nich program wypisujący pełne podsumowanie dla trzech ocen. Żadna metoda nie może wypisywać wyników — od tego jest Main.
Co trzeba zapamiętać
- Parametr to kopia — zmiana parametru nie dotyka zmiennej wywołującego.
refdaje dostęp do oryginału; słowo piszemy w deklaracji i przy wywołaniu.outsłuży do oddania dodatkowego wyniku i musi zostać przypisany w metodzie.- Parametry opcjonalne stoją na końcu listy, a argument nazwany pozwala pominąć środkowy.
- Przeciążenia różnią się liczbą lub typami parametrów — nigdy samym typem wyniku.
- Zmienne zadeklarowane w metodzie nie istnieją poza nią; dane przekazujemy parametrami i zwracamy przez
return.
Dokumentacja: Microsoft Learn — temat tej lekcji.