• C# i .NET
  • Jak definiować elementy w C#? Klasy, metody i #define

Jak definiować elementy w C#? Klasy, metody i #define

Radosław Krajewski 19 maja 2026
Kod C# pokazujący implementację częściowej metody `OnNameChanged` w klasie `Person`.

Spis treści

W C# nie ma jednego uniwersalnego polecenia „define”. Element programu definiuje się za pomocą odpowiedniej konstrukcji języka, na przykład class dla typu, typu danych dla zmiennej oraz sygnatury dla metody. Osobnego wyjaśnienia wymaga zapis #define, ponieważ służy on do definiowania symboli kompilacji warunkowej, a nie klas, metod ani wartości stałych.

Definiowanie w C# opiera się na konstrukcjach języka, nie na jednym poleceniu

  • Klasa opisuje typ obiektu i jego zachowanie.
  • Metoda definiuje operację, którą może wykonać program.
  • Zmienna, pole i stała przechowują dane, ale mają różny zakres i zastosowanie.
  • #define tworzy symbol używany przy kompilacji warunkowej.
  • const lub static readonly zastępują klasyczne makra z innych języków.

Kod C# w Visual Studio, definiujący klasę Game1 dla gry Asteroids, z użyciem XNA Framework.

Co w C# naprawdę oznacza definiowanie elementu

W rozmowach o programowaniu słowo „definiować” bywa używane szeroko. W C# może oznaczać utworzenie nowej klasy, zadeklarowanie zmiennej, opisanie parametrów metody albo przygotowanie symbolu, który wpłynie na przebieg kompilacji.

Najważniejsze jest rozróżnienie dwóch grup. Elementy języka, takie jak klasy, metody i zmienne, są częścią struktury programu i działają w czasie wykonywania. Dyrektywa #define działa wcześniej, na etapie przygotowania kodu do kompilacji, dlatego nie tworzy obiektu ani wartości dostępnej podczas działania aplikacji.

Ja zwykle zaczynam od pytania, co dokładnie chcę zdefiniować. Jeśli opisuję rzecz biznesową, potrzebuję klasy. Jeśli chcę wykonać operację, tworzę metodę. Gdy przechowuję dane, wybieram zmienną, pole albo stałą. Taki prosty podział eliminuje większość nieporozumień związanych z tym tematem.

Jak definiować klasę, obiekt i jego składowe

Klasa jest szablonem opisującym dane i zachowania obiektu. Definiuje się ją za pomocą słowa kluczowego class, po którym podaje się nazwę typu.

public class User
{
    public string Name { get; set; } = string.Empty;

    public bool IsAdult()
    {
        return Age >= 18;
    }

    public int Age { get; set; }
}

W tym przykładzie klasa User ma dwie właściwości oraz metodę. Właściwości opisują stan użytkownika, a metoda określa zachowanie. To ważna granica: klasa nie jest jeszcze konkretnym użytkownikiem, tylko opisem tego, jak taki obiekt ma wyglądać.

Obiekt tworzę dopiero za pomocą operatora new:

User user = new User
{
    Name = "Anna",
    Age = 29
};

bool result = user.IsAdult();

Od tego momentu zmienna user przechowuje referencję do konkretnej instancji klasy. W praktyce warto od razu nadawać klasom jedną odpowiedzialność. Klasa OrderService może obsługiwać zamówienia, ale dokładanie do niej logiki wysyłki wiadomości, generowania raportów i autoryzacji szybko prowadzi do trudnego w utrzymaniu kodu.

Widoczność elementów klasy

Modyfikatory dostępu

decydują, kto może korzystać z danego elementu. Najczęściej używam public dla części przeznaczonej dla innych klas oraz private dla szczegółów implementacyjnych.

public class BankAccount
{
    private decimal balance;

    public decimal Balance => balance;

    public void Deposit(decimal amount)
    {
        if (amount <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(amount));
        }

        balance += amount;
    }
}

Takie podejście chroni stan obiektu. Kod z zewnątrz nie może dowolnie ustawić salda, tylko musi przejść przez metodę Deposit, która sprawdza poprawność danych. To drobny przykład enkapsulacji, czyli ukrywania szczegółów i kontrolowania dostępu do nich.

Jak definiować metody i parametry

Metoda opisuje czynność wykonywaną przez klasę, rekord albo strukturę. Jej definicja składa się zwykle z modyfikatora dostępu, typu zwracanego, nazwy, listy parametrów oraz ciała umieszczonego w nawiasach klamrowych.

public decimal CalculateNetPrice(decimal grossPrice, decimal vatRate)
{
    return grossPrice / (1 + vatRate);
}

W tym przypadku metoda zwraca wartość typu decimal i przyjmuje dwie wartości wejściowe. Typ parametrów nie jest formalnością. Dzięki niemu kompilator może wychwycić część błędów, zanim aplikacja zostanie uruchomiona.

Jeżeli metoda niczego nie zwraca, używam typu void:

public void PrintMessage(string message)
{
    Console.WriteLine(message);
}

Gdy metoda ma prostą postać, można zapisać ją jako wyrażenie lambda z operatorem =>:

public int Add(int first, int second) => first + second;

Nie traktuję krótszego zapisu jako automatycznie lepszego. Przy jednej operacji poprawia czytelność, ale przy bardziej złożonej logice klasyczne ciało metody ułatwia debugowanie i rozbudowę.

Metoda statyczna a metoda instancji

Metoda bez słowa static działa na konkretnej instancji klasy i może korzystać z jej stanu. Metoda statyczna należy do samego typu, dlatego wywołuje się ją bez tworzenia obiektu.

public class TemperatureConverter
{
    public static double CelsiusToFahrenheit(double celsius)
    {
        return celsius * 9 / 5 + 32;
    }
}

double fahrenheit = TemperatureConverter.CelsiusToFahrenheit(20);

Metody statyczne dobrze pasują do czystych przeliczeń i funkcji, które nie potrzebują danych konkretnego obiektu. Jeśli operacja korzysta z pól lub właściwości instancji, dodawanie static będzie złym kierunkiem i utrudni modelowanie odpowiedzialności.

Jak definiować zmienne, pola i stałe

Zmienna lokalna istnieje w określonym zakresie, najczęściej wewnątrz metody. Definiuję ją przez podanie typu i nazwy, a opcjonalnie od razu przypisuję wartość.

int attempts = 3;
string environment = "Production";
bool isEnabled = true;

C# pozwala także użyć var. Nie oznacza to braku typu. Kompilator nadal ustala konkretny typ na podstawie wartości przypisanej podczas kompilacji.

var count = 10;          // int
var name = "Kasia";      // string
var prices = new List();

Stosuję var, gdy typ wynika jasno z prawej strony wyrażenia. Przy liczbach bez oczywistego kontekstu jawny typ, taki jak decimal albo long, często lepiej komunikuje intencję.

Pole, stała i wartość tylko do odczytu

Pole jest zmienną należącą do klasy lub struktury. W przeciwieństwie do zmiennej lokalnej może przechowywać stan obiektu przez cały czas jego życia.

public class Counter
{
    private int value;

    public void Increment()
    {
        value++;
    }
}

Jeżeli wartość jest znana podczas kompilacji i nie powinna się zmieniać, używam const:

public const int MaxRetries = 3;
public const string DefaultRegion = "eu-central";

Stała musi otrzymać wartość możliwą do ustalenia na etapie kompilacji. Gdy wartość ma zostać wyznaczona dopiero podczas uruchomienia, lepszym wyborem będzie static readonly:

public static readonly DateTime ApplicationStarted = DateTime.UtcNow;

To rozróżnienie ma praktyczne znaczenie. const jest częścią kontraktu kompilacyjnego, natomiast static readonly pozwala ustalić wartość w konstruktorze lub podczas inicjalizacji aplikacji.

Do czego służy #define i kiedy go unikać

Dyrektywa #define definiuje symbol preprocesora. Symbol nie przechowuje wartości i nie jest zmienną. Można go później sprawdzić za pomocą #if, aby zdecydować, który fragment kodu zostanie uwzględniony podczas kompilacji.

#define ENABLE_DIAGNOSTICS

using System;

#if ENABLE_DIAGNOSTICS
Console.WriteLine("Diagnostyka jest włączona");
#endif

Własny symbol musi znaleźć się odpowiednio wcześnie w pliku, przed zwykłymi instrukcjami kodu. W większych projektach częściej definiuje się go w ustawieniach projektu lub konfiguracji kompilacji, dzięki czemu można używać różnych symboli dla środowiska testowego i produkcyjnego.

Typowym zastosowaniem jest warunkowe dołączenie kodu diagnostycznego:

#if DEBUG
Console.WriteLine("Kod uruchomiony w trybie debugowania");
#endif

Trzeba jednak pamiętać, że #define nie działa jak makro z C lub C++. Taki zapis nie definiuje liczby ani tekstu:

#define MAX_USERS 100

Jeśli chcę nazwać wartość, używam stałej:

const int MaxUsers = 100;

W mojej praktyce preprocesor sprawdza się przy niewielkich różnicach między konfiguracjami, na przykład przy logowaniu diagnostycznym lub kodzie zależnym od platformy. Nadużywanie #if tworzy jednak kilka wersji tego samego programu w jednym pliku. Gdy warunki zaczynają dominować nad logiką, lepiej zastosować konfigurację, interfejs albo wstrzykiwanie zależności.

Najczęstsze błędy przy definiowaniu elementów C#

Początkujący często próbują używać #define do zastępowania stałych. To przenosi nawyki z innych języków i odbiera kodowi typowanie. W C# bezpieczniej jest nazwać wartość przez const, a zachowanie zamknąć w metodzie.

Drugim problemem jest umieszczanie całego stanu klasy w polach publicznych. Taki kod działa, ale każdy fragment aplikacji może zmieniać dane bez żadnej kontroli. Prywatne pola oraz publiczne metody i właściwości pozwalają narzucić reguły, które chronią obiekt przed niepoprawnym użyciem.

Spotykam też metody przyjmujące zbyt wiele parametrów. Cztery lub pięć argumentów nie zawsze oznacza błąd, ale przy większej liczbie często warto zdefiniować osobny typ, na przykład record przechowujący dane wejściowe.

public record CreateUserRequest(
    string Name,
    string Email,
    int Age
);

public User CreateUser(CreateUserRequest request)
{
    return new User
    {
        Name = request.Name,
        Age = request.Age
    };
}

Najprostsza reguła brzmi tak: typ opisuje dane, metoda opisuje działanie, a zmienna przechowuje konkretną wartość. Gdy konstrukcja zaczyna pełnić kilka tych ról naraz, kod zwykle wymaga podziału.

Jak wybrać właściwą konstrukcję w codziennym kodzie

Potrzeba Najlepsza konstrukcja Przykład
Opis obiektu i jego zachowania class class Invoice
Jednorazowa wartość wewnątrz metody zmienna lokalna int total = 10;
Stan przechowywany przez obiekt pole lub właściwość private decimal balance;
Wartość niezmienna znana podczas kompilacji const const int Limit = 10;
Operacja wykonywana przez typ metoda CalculateTotal()
Włączenie lub pominięcie kodu przy kompilacji #define i #if #if DEBUG

To zestawienie dobrze pokazuje, że słowo „define” w pytaniach o C# może prowadzić do kilku różnych odpowiedzi. Najpierw trzeba rozpoznać, czy chodzi o strukturę programu, dane, zachowanie czy konfigurację procesu kompilacji.

Od właściwej definicji do prostszego kodu C#

Jeżeli tworzę nowy fragment aplikacji, zaczynam od nazwania odpowiedzialności. Dla danych wybieram typ, dla czynności metodę, dla większego modelu klasę, a dla niezmiennej wartości stałą. Dyrektywy #define zostawiam na sytuacje, w których naprawdę potrzebuję warunkowo dołączyć kod.

Takie podejście ogranicza liczbę wyjątków i sprawia, że kod łatwiej czytać po kilku miesiącach. W C# nie wygrywa konstrukcja najbardziej zbliżona do rozwiązań z innych języków, tylko ta, która najlepiej pokazuje intencję programu.

FAQ - Najczęstsze pytania

Nie. #define tworzy symbol preprocesora używany z #if do warunkowego dołączania kodu podczas kompilacji. Wartości stałe definiuje się za pomocą const, a wartości ustalane podczas uruchomienia za pomocą static readonly.

Klasa jest szablonem opisującym dane i zachowania, natomiast obiekt jest konkretną instancją tej klasy. Obiekt tworzy się operatorem new, na przykład User user = new User, a następnie można korzystać z jego właściwości i metod.

Zmienna lokalna działa w określonym zakresie, najczęściej wewnątrz metody. Pole należy do klasy lub struktury i przechowuje stan obiektu, a const przechowuje niezmienną wartość znaną już podczas kompilacji.

Metoda statyczna pasuje do operacji, które nie korzystają ze stanu konkretnego obiektu, na przykład przeliczenia temperatury. Wywołuje się ją bez tworzenia instancji klasy. Jeśli operacja potrzebuje pól lub właściwości instancji, metoda nie powinna być statyczna.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

klasy
metody
zmienne
stałe
preprocesor
Autor Radosław Krajewski
Radosław Krajewski
Nazywam się Radosław Krajewski i od 6 lat zgłębiam tajniki programowania .NET, chmury Azure oraz sztucznej inteligencji. Moja przygoda z tymi technologiami zaczęła się od fascynacji tym, jak złożone problemy można rozwiązywać za pomocą kodu i innowacyjnych narzędzi. Staram się przekazywać tę wiedzę w sposób zrozumiały, dzieląc się swoimi doświadczeniami i spostrzeżeniami na kursdotnet.pl. W moich artykułach skupiam się na praktycznych aspektach, porównuję różne rozwiązania i analizuję najnowsze trendy, aby dostarczyć Wam rzetelne i aktualne informacje, które pomogą Wam rozwijać się w tej dynamicznie zmieniającej się dziedzinie.

Udostępnij artykuł

Napisz komentarz