Funkcje (metody) w C#

Funkcje — w C# nazywane metodami — pozwalają grupować logikę w nazwane, wielokrotnie używane bloki. To fundament czytelnego i utrzymywalnego kodu. Poniżej kompletny przegląd: od składni po techniki używane w realnych projektach.
Składnia deklaracji metody
[modyfikatory] [typ-zwracany] NazwaMetody([parametry])
{
// ciało metody
}- modyfikatory – np.
public,private,static,async. - typ zwracany – typ wyniku lub
void, gdy metoda nic nie zwraca. - parametry – lista danych wejściowych (typ + nazwa).
Definicja, wywołanie i typ zwracany
public int Dodaj(int a, int b)
{
return a + b;
}
int suma = Dodaj(12, 31); // wywołanie, suma = 43
// metoda bez zwracanej wartości:
public void WyswietlPowitanie(string imie)
{
Console.WriteLine($"Witaj, {imie}!");
}Modyfikatory dostępu
Sterują widocznością metody: public (dostępna wszędzie), private (tylko w obrębie klasy), protected (klasa i klasy pochodne), internal (w obrębie tego samego assembly). Domyślnie składowe klasy są private.
Parametry i wartości domyślne
public int PodniesDoPotegi(int liczba, int potega)
{
int wynik = 1;
for (int i = 0; i < potega; i++)
{
wynik *= liczba;
}
return wynik;
}
// wartości domyślne — można pominąć argument przy wywołaniu
public void Przywitaj(string imie = "Anonim", int wiek = 30)
{
Console.WriteLine($"Witaj, {imie}! Wiek: {wiek}.");
}
Przywitaj("Jan"); // Witaj, Jan! Wiek: 30.
Przywitaj("Anna", 25); // Witaj, Anna! Wiek: 25.Przekazywanie przez referencję: ref i out
Domyślnie typy wartościowe przekazywane są przez wartość (kopia). ref pozwala metodzie zmienić oryginalną zmienną (musi być zainicjowana przed wywołaniem), a out służy do zwrócenia wartości przez parametr (inicjujemy ją w metodzie).
public void ZmienLiczbe(ref int liczba)
{
liczba += 10;
}
public bool ProbaKonwersji(string tekst, out int wynik)
{
return int.TryParse(tekst, out wynik);
}Przeciążanie metod
Możesz mieć wiele metod o tej samej nazwie, różniących się liczbą lub typami parametrów (sygnaturą). Kompilator wybierze właściwą na podstawie argumentów.
public int Dodaj(int a, int b) => a + b;
public double Dodaj(double a, double b) => a + b;Metody statyczne
Należą do klasy, a nie do konkretnego obiektu — wywołasz je bez tworzenia instancji.
public static int Podwoj(int liczba) => liczba * 2;
int wynik = Kalkulator.Podwoj(21); // 42Metody asynchroniczne (async / await)
Pozwalają wykonywać operacje wejścia/wyjścia (sieć, dysk, baza danych) bez blokowania wątku. Metoda asynchroniczna zwraca zwykle Task lub Task<T>.
public async Task<string> PobierzDaneAsync()
{
using var client = new HttpClient();
return await client.GetStringAsync("https://example.com");
}Obsługa wyjątków w metodzie
public int Dziel(int dzielna, int dzielnik)
{
try
{
return dzielna / dzielnik;
}
catch (DivideByZeroException)
{
Console.WriteLine("Nie można dzielić przez zero.");
return 0;
}
}Metody jako argumenty: delegaty i lambdy
W C# metody można przekazywać jak dane. Służą do tego delegaty (typy reprezentujące „wskaźnik na metodę”) oraz gotowe typy Action / Func. Najkrótszą formą zapisu są wyrażenia lambda.
// własny delegat
public delegate int OperacjaMatematyczna(int a, int b);
OperacjaMatematyczna dodawanie = (a, b) => a + b;
int suma = dodawanie(2, 3); // 5
// metoda przyjmująca inną metodę jako argument
public void WykonajAkcje(Action akcja) => akcja();
WykonajAkcje(() => Console.WriteLine("Wykonano akcję"));
// lambda jako argument metody LINQ
List liczby = new() { 1, 2, 3, 4, 5 };
List parzyste = liczby.Where(x => x % 2 == 0).ToList(); // 2, 4Zdarzenia (events)
Zdarzenia to mechanizm powiadamiania oparty na delegatach — pozwala odseparować nadawcę od odbiorców.
public event Action<string> WiadomoscOdebrana;
public void Odbierz(string tresc)
{
WiadomoscOdebrana?.Invoke(tresc); // powiadom subskrybentów, jeśli są
}Metody rozszerzeń (extension methods)
Dodają nowe metody do istniejących typów bez modyfikowania ich kodu. To metody statyczne w statycznej klasie, z pierwszym parametrem oznaczonym this.
public static class Rozszerzenia
{
public static bool JestParzysta(this int liczba) => liczba % 2 == 0;
}
bool wynik = 6.JestParzysta(); // trueMetody generyczne
Działają na wielu typach przy zachowaniu kontroli typów w czasie kompilacji. Ograniczenia (where) precyzują, co dany typ musi spełniać.
public T ZnajdzMax<T>(T[] tablica) where T : IComparable<T>
{
T max = tablica[0];
foreach (T element in tablica)
{
if (element.CompareTo(max) > 0)
{
max = element;
}
}
return max;
}Wartość domyślna typu generycznego — default(T), nie new T()
Częsta pułapka. Jeśli chcesz zwrócić wartość domyślną (czyli null dla typów referencyjnych, 0 dla int itd.), użyj default(T) — bez ograniczenia where T : new():
public T Domyslna<T>() => default!; // default(T)
int liczba = Domyslna<int>(); // 0
string? tekst = Domyslna<string>(); // nullDlaczego nie new T()? Ograniczenie where T : new() wymaga, by typ miał bezparametrowy konstruktor — a string go nie ma, więc Domyslna<string>() z new T() nie skompilowałoby się (błąd CS0310). Dodatkowo new T() tworzy nowy obiekt i nigdy nie zwróci null. Konstrukcji new T() używaj tylko wtedy, gdy faktycznie chcesz utworzyć nową instancję typu, który ten konstruktor posiada.
Przeciążanie operatorów
Dla własnych typów możesz zdefiniować zachowanie operatorów (np. +), co bywa wygodne w typach matematycznych czy Value Objects.
public static Wektor operator +(Wektor a, Wektor b)
=> new Wektor(a.X + b.X, a.Y + b.Y);Dobre praktyki
- Jedna odpowiedzialność (SRP) – metoda powinna robić jedną rzecz i robić ją dobrze. Krótkie metody łatwiej zrozumieć i testować.
- Wstrzykiwanie zależności (DI) – zależności przekazuj przez konstruktor, zamiast tworzyć je wewnątrz. Kod staje się elastyczny i testowalny.
- Testowanie jednostkowe – pokrywaj logikę testami (xUnit, NUnit, MSTest).
- Walidacja parametrów – w metodach publicznych sprawdzaj dane wejściowe (np.
ArgumentNullException.ThrowIfNull(...)). - Czytelne nazwy – nazwa metody powinna mówić, co robi (czasownik + obiekt, np.
ObliczPodatek).
public class Serwis
{
private readonly IRepozytorium _repozytorium;
public Serwis(IRepozytorium repozytorium) // DI przez konstruktor
{
_repozytorium = repozytorium;
}
public void WykonajAkcje()
{
// korzystamy z _repozytorium
}
}Podsumowanie
Metody to podstawowy budulec aplikacji w C#. Zaczynasz od składni, parametrów i typu zwracanego, a z czasem sięgasz po przeciążanie, generyki, delegaty i lambdy oraz async/await. Niezależnie od poziomu zaawansowania trzymaj się dobrych praktyk — jedna odpowiedzialność, czytelne nazwy, walidacja danych i testy — bo to one decydują o jakości kodu.
5 comments
Dodaj komentarz
Musisz się zalogować, aby móc dodać komentarz.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →
Dobrze napisane! Chciałbym zobaczyć więcej na temat metod asynchronicznych. Czy możesz przedstawić bardziej zaawansowane scenariusze użycia, zwłaszcza jeśli chodzi o równoczesne operacje?
Bardzo przydatny post! Lubię, że wspomniałeś o metodach rozszerzeń – to narzędzie może naprawdę ułatwić życie podczas pracy z istniejącymi klasami. Czy możesz dostarczyć więcej przykładów użycia metod rozszerzeń?
Świetny przegląd! Chociaż na początku trudno jest zrozumieć wszystko, co związanego z C#, szczególnie dla nowicjuszy. Mam nadzieję, że rozwiniesz temat obsługi wyjątków i refleksji w przyszłych wpisach.
Dobre wprowadzenie do tematu! Bardzo podoba mi się, że dodajesz bardziej zaawansowane koncepcje, takie jak delegaty i wyrażenia lambda. Czy mogłeśbyś przedstawić więcej przykładów zastosowania delegatów w praktyce?
Świetne podsumowanie funkcji w C#! Bardzo pomocne dla tych, którzy dopiero zaczynają z tym językiem. Cieszę się, że wspomniałeś o modyfikatorach dostępu i wartościach domyślnych, to często pomijane aspekty.