🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

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);   // 42

Metody 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, 4

Zdarzenia (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();   // true

Metody 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>();   // null

Dlaczego 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.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

5 comments

  1. 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?

  2. 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ń?

  3. Ś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.

  4. 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?

  5. Ś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.

Dodaj komentarz

czytanie to początek

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ę →