cs

Tworzenie klas, obiektów i struktur w języku C#.

Klasa – to szablon (definicja) opisujący strukturę i zachowanie obiektów. Klasa określa pola (zmienne) przechowujące stan obiektu oraz metody (funkcje) definiujące jego zachowanie.

Obiekt – konkretna instancja klasy, czyli rzeczywisty element utworzony na podstawie szablonu (klasy). Każdy obiekt ma swoje unikalne wartości atrybutów.

Struktura – typ wartościowy przechowywany na stosie (stack), lżejszy niż klasa. Nie obsługuje dziedziczenia, ale może mieć właściwości i metody.

Rekord – specjalny typ danych służący do przechowywania niemutowalnych danych. Może być referencyjny (class) lub wartościowy (struct). Automatycznie generuje metody porównujące zawartość.

Hermetyzacja (enkapsulacja) – jedna z podstawowych zasad programowania obiektowego (OOP) polegająca na ukrywaniu szczegółów implementacji klasy i udostępnianiu tylko niezbędnych danych oraz metod. Zapewnia bezpieczeństwo i kontrolę nad dostępem do pól oraz właściwości obiektu (hermetyzacja to też dbanie by np. cena nie mogła być ujemna).

Członkowie klasy/struktury/rekordu

Pola (ang. fields) - zmienne zadeklarowane w zakresie klasy. Mogą przechowywać dane, takie jak liczby, teksty lub obiekty innych klas. Przykład: private int wiek;. Niezainicjalizowanie pól klasy spowoduje że będą one przechowywać domyślne wartości.

Pola klasy definiujemy zazwyczaj na początku definicji klasy/struktury według dobrych praktyk. Poniżej pokazano przykładowe definicje pól klasy wraz z dobrymi praktykami.

public struct Example{
     // Publiczne pole typu int
    public int Number;  // PascalCase dla publicznych pól
    // Prywatne pole typu string
    private string _mame;  // camelCase dla prywatnych pól z podkreślemien _ na początku.
    // Static pole - wspólne dla wszystkich instancji struktury
    public static int ObjectCount;  // PascalCase dla pól statycznych
    // Pole readonly - tylko do odczytu po ustawieniu w konstruktorze
    public readonly double Pi;  // PascalCase dla pól readonly
    // Pole typu referencyjnego (klasa) - np. inna instancja klasy
    private DateTime _birthDate;  // camelCase dla prywatnych pól ze znakiem podkreślenia _
    // Pole typu tablica
    public int[] Numbers;  // PascalCase dla publicznych pól, camelCase dla prywatnych
    // Pole typu Nullable (np. int, które może przechowywać 'null')
    public int? Age;  // camelCase dla prywatnych pól, PascalCase dla publicznych pól
    // Pole typu kolekcja (List)
    public System.Collections.Generic.List<string> NamesList;  // PascalCase dla publicznych pól
}

Stałe (ang. constants) - to pola, których wartość jest ustalana w czasie kompilacji i nie może być zmieniona. Są zazwyczaj deklarowane za pomocą słowa kluczowego const. Wartość stałej nie może być zmieniana po przypisaniu. Stałe domyślnie są statyczne co za tym idzie współdzielone pomiędzy wszystkimi instancjami klasy.

class Example{
    // Pole typu const - stała wartość ustawiana w czasie kompilacji
    public const double SPEED_OF_LIGHT = 299792458;  // ALL_CAPS dla stałych, słowa oddzielane podkreśleniami
}

Właściwości (ang. properties) - to mechanizm, który pozwala na bezpieczne i kontrolowane operacje na polach obiektu. Właściwości mogą pełnić funkcję podobną do publicznych pól, ale oferują większą elastyczność, pozwalając na: - Walidację przy ustawianiu wartości. - Przekształcanie danych przy pobieraniu lub ustawianiu. - Ukrywanie implementacji dostępu do danych obiektu (enkapsulacja). Właściwości są deklarowane przy użyciu słów kluczowych get i set

public <typ> <nazwa> { 
    get { return <pole>; }  // Getter
    set { <pole> = value; }  // Setter
}
public class Example{
    private string name;  // Pole prywatne
    // Właściwość, która pozwala na dostęp do pola 'name'
    public string Name
    {
        get { return name; }  // Getter: zwraca wartość pola
        set { name = value; } // Setter: przypisuje wartość do pola
    }

    public string Surname { get; set; }  // Automatyczna właściwość z domyślnym polem (Pole surname zostanie automatycznie utworzone)

    private short status;

    public short Status
    {
        get { return status; }  // Tylko getter, brak settera
    }
    
    private string street;
    
    public string Street
    {
        set { street = value; }  // Tylko setter, brak gettera
    }

    private int age;
    // Właściwość z walidacją
    public int Age
    {
        get { return age; }
        set 
        { 
            if (value < 0) 
                throw new ArgumentException("Wiek nie może być ujemny");
            age = value; 
        }
    }
}

Metody (ang. methods) - definiują akcje, które może wykonywać klasa. Mogą przyjmować parametry wejściowe i zwracać dane wyjściowe.

public class Example
{
    // Metoda instancyjna
    public void InstanceMethod()
    {
        Console.WriteLine("Instancyjna metoda");
    }

    // Metoda statyczna
    public static void StaticMethod()
    {
        Console.WriteLine("Statyczna metoda");
    }

    // Metoda z wartością zwracaną
    public int Add(int a, int b)
    {
        return a + b;
    }

    // Metoda bez wartości zwracanej
    public void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }

    // Metoda z domyślnym parametrem
    public void Greet(string name = "Guest")
    {
        Console.WriteLine($"Hello, {name}");
    }

    // Metoda z parametrami zmiennymi (params)
    public void SumNumbers(params int[] numbers)
    {
        int sum = numbers.Sum();
        Console.WriteLine($"Sum: {sum}");
    }

    // Metoda asynchroniczna
    public async Task<string> GetDataAsync()
    {
        await Task.Delay(1000); // symulacja operacji asynchronicznej
        return "Data fetched";
    }

    // Metoda z użyciem ref
    public void Increment(ref int value)
    {
        value++;
    }

    // Metoda z użyciem out
    public void GetValues(out int a, out int b)
    {
        a = 5;
        b = 10;
    }

    // Metoda wirtualna - można ją nadpisać
    public virtual void Speak()
    {
        Console.WriteLine("Animal speaks");
    }

    // Metoda sealed - nie można jej nadpisać
    public sealed override void Speak2() {
        Console.WriteLine("Dog barks");
    }

    // Metoda nadpisana
    public override void Speak()
    {
        Console.WriteLine("Dog barks");
    }

    // Metoda przeciążona
    public int Add(int a, int b) { return a + b; }
    public double Add(double a, double b) { return a + b; }
    public string Add(string a, string b) { return a + b; }

    // Metoda lokalna
    public void OuterMethod()
    {
        void InnerMethod()
        {
            Console.WriteLine("This is a local method");
        }

        InnerMethod(); // Wywołanie metody lokalnej
    }

    // Metoda z delegatem
    public delegate void Notify(string message);
    public void NotifyUser(string message)
    {
        Console.WriteLine(message);
    }
}

Zdarzenia (events) - pozwalają na powiadamianie innych obiektów o określonych zdarzeniach (np. kliknięcie przycisku). Są związane z delegatami i mogą być wyzwalane w klasie. Zdarzenia są oparte na delegatach i pozwalają na obsługę zdarzeń w sposób luźno powiązany.

using System;

// Delegat, który określa sygnaturę metody obsługującej zdarzenie
public delegate void MyEventHandler(object sender, string message);

public class Publisher
{
    // Zdarzenie, które może być wywołane
    public event MyEventHandler MyEvent;

    // Metoda do wywoływania zdarzenia
    public void TriggerEvent(string message)
    {
        Console.WriteLine("Zdarzenie zostało wywołane!");

        // Sprawdzenie, czy ktoś subskrybuje zdarzenie
        MyEvent?.Invoke(this, message);
    }
}

public class Subscriber
{
    // Metoda, która będzie obsługiwała zdarzenie
    public void OnMyEventTriggered(object sender, string message)
    {
        Console.WriteLine($"Zdarzenie zostało obsłużone. Otrzymana wiadomość: {message}");
    }
}

public class Program
{
    public static void Main()
    {
        // Tworzenie obiektów publishera i subskrybenta
        Publisher publisher = new Publisher();
        Subscriber subscriber = new Subscriber();

        // Subskrypcja na zdarzenie
        publisher.MyEvent += subscriber.OnMyEventTriggered;

        // Wywołanie zdarzenia z wiadomością
        publisher.TriggerEvent("Hello, this is a custom message!");
    }
}

Operatory - Przeciążone operatory pozwalają na modyfikowanie standardowego zachowania operatorów (np. +, -) dla własnych typów. Definiowane jako metody statyczne w klasie.

using System;

class Punkt
{
    public int X { get; }
    public int Y { get; }

    public Punkt(int x, int y)
    {
        X = x;
        Y = y;
    }

    public static Punkt operator +(Punkt p1, Punkt p2)
    {
        return new Punkt(p1.X + p2.X, p1.Y + p2.Y);
    }

    public override string ToString() => $"({X}, {Y})";
}

class Program
{
    static void Main()
    {
        Punkt a = new Punkt(2, 3);
        Punkt b = new Punkt(4, 5);
        Punkt c = a + b;
        Console.WriteLine(c); // Wynik: (6, 8)
    }
}

Indeksatory - umożliwiają dostęp do obiektów klasy w sposób przypominający tablice. Przykład: public string this[int index] umożliwia indeksowanie obiektu klasy.

using System;

class Tablica
{
    private string[] dane = new string[5];

    public string this[int index]
    {
        get { return dane[index]; }
        set { dane[index] = value; }
    }
}

class Program
{
    static void Main()
    {
        Tablica tablica = new Tablica();
        tablica[0] = "Pierwszy";
        tablica[1] = "Drugi";
        Console.WriteLine(tablica[0]); // Wynik: Pierwszy
    }
}

Konstruktory (ang. constructors) - są specjalnymi metodami, wywoływanymi podczas tworzenia obiektu. Służą do inicjowania danych obiektu, np. public Klasa(int a, int b).

using System;

class Osoba
{
    public string Imie { get; }
    public int Wiek { get; }

    public Osoba(string imie, int wiek)
    {
        Imie = imie;
        Wiek = wiek;
    }

    public void PrzedstawSie()
    {
        Console.WriteLine($"Cześć, jestem {Imie}, mam {Wiek} lat.");
    }
}

class Program
{
    static void Main()
    {
        Osoba osoba = new Osoba("Jan", 25);
        osoba.PrzedstawSie(); // Wynik: Cześć, jestem Jan, mam 25 lat.
    }
}

Finalizatory (destruktory) - (metody ~NazwaKlasy) są wywoływane, gdy obiekt jest usuwany z pamięci, pomagają w zwolnieniu zasobów (rzadko używane w C#).

using System;

class Zasoby
{
    public Zasoby()
    {
        Console.WriteLine("Zasób został utworzony.");
    }

    ~Zasoby()
    {
        Console.WriteLine("Zasób został zwolniony.");
    }
}

class Program
{
    static void Main()
    {
        Zasoby zasob = new Zasoby();
    }
}

Zagnieżdżone typy - Zagnieżdżone typy to typy zdefiniowane wewnątrz innego typu (np. klasy, struktury). Używane do organizowania kodu lub do tworzenia typów pomocniczych wewnątrz klasy.

using System;

class Zewnetrzna
{
    public class Wewnetrzna
    {
        public void Wyswietl()
        {
            Console.WriteLine("To jest klasa zagnieżdżona!");
        }
    }
}

class Program
{
    static void Main()
    {
        Zewnetrzna.Wewnetrzna obiekt = new Zewnetrzna.Wewnetrzna();
        obiekt.Wyswietl(); // Wynik: To jest klasa zagnieżdżona!
    }
}

Modyfikatory dostępu

public – dostępny z każdego miejsca

private – dostępny tylko wewnątrz klasy (domyślny dla pól)

protected – dostępny w klasie i klasach dziedziczących

internal – dostępny w obrębie tego samego projektu

protected internal – połączenie protected i internal.vMożna go użyć w dwóch przypadkach: - W obrębie tego samego zestawu (czyli jak internal). - W klasach dziedziczących, nawet jeśli są w innych projektach (czyli jak protected).

private protected - dostępny tylko w tej samej klasie i jej pochodnych, ale tylko w tym samym projekcie.

file - element (np. klasa) jest dostępny tylko w obrębie pliku źródłowego, w którym został zadeklarowany.

this

Słowo kluczowe this w C# odnosi się do bieżącego obiektu klasy. Może być używane w różnych kontekstach, takich jak dostęp do pól i metod klasy, wywoływanie innych konstruktorów, a także do rozwiązywania konfliktów nazw (pomaga rozróżnić pola i parametry o tej samej nazwie).

przykład 1

Używane, gdy nazwy parametrów w metodach lub konstruktorach są takie same jak nazwy pól klasy.

class Osoba
{
    private string imie;

    public Osoba(string imie)
    {
        this.imie = imie; // `this` odwołuje się do pola klasy
    }

    public void PrzedstawSie()
    {
        Console.WriteLine($"Cześć, jestem {this.imie}!");
    }
}

class Program
{
    static void Main()
    {
        Osoba osoba = new Osoba("Jan");
        osoba.PrzedstawSie(); // Wynik: Cześć, jestem Jan!
    }
}

przykład 2

Pozwala na wywołanie innego konstruktora w tej samej klasie, zmniejszając powtarzalność kodu.

class Osoba
{
    public string Imie { get; }
    public int Wiek { get; }

    public Osoba(string imie) : this(imie, 18) // Wywołuje drugi konstruktor
    {
    }

    public Osoba(string imie, int wiek)
    {
        Imie = imie;
        Wiek = wiek;
    }
}

class Program
{
    static void Main()
    {
        Osoba osoba1 = new Osoba("Adam");
        Osoba osoba2 = new Osoba("Ewa", 25);

        Console.WriteLine($"{osoba1.Imie}, {osoba1.Wiek} lat"); // Wynik: Adam, 18 lat
        Console.WriteLine($"{osoba2.Imie}, {osoba2.Wiek} lat"); // Wynik: Ewa, 25 lat
    }
}

przykład 3

Metody rozszerzające pozwalają dodać nowe funkcje do istniejących typów bez ich modyfikowania.

static class StringExtensions // W C# metody rozszerzające muszą znajdować się w statycznej klasie. StringExtensions nie dziedziczy po string, ale może definiować metody dla string. Dzięki temu można ją wywoływać jakby była częścią klasy string, mimo że string nie został zmodyfikowany.
{
    public static string PowiekszPierwszaLitere(this string tekst) //Parametr this string tekst oznacza, że ta metoda rozszerza typ string. 
    {
        if (string.IsNullOrEmpty(tekst)) return tekst;
        return char.ToUpper(tekst[0]) + tekst.Substring(1);
    }
}

class Program
{
    static void Main()
    {
        string slowo = "csharp";
        Console.WriteLine(slowo.PowiekszPierwszaLitere()); // Wynik: Csharp - slowo to zwykły string, ale dzięki metodzie rozszerzającej możemy wywołać PowiekszPierwszaLitere() tak, jakby string miał tę metodę natywnie.
    }
}

przykład 4

Umożliwia wygodny dostęp do elementów obiektu.

class Sklep
{
    private string[] produkty = { "Jabłko", "Banan", "Gruszka" };

    public string this[int index]
    {
        get { return produkty[index]; }
        set { produkty[index] = value; }
    }
}

class Program
{
    static void Main()
    {
        Sklep sklep = new Sklep();
        Console.WriteLine(sklep[1]); // Wynik: Banan
        sklep[1] = "Pomarańcza";
        Console.WriteLine(sklep[1]); // Wynik: Pomarańcza
    }
}

Zadania

Zadanie 1

Stwórz klasę Book, która przechowuje dane o książce:

W metodzie Main stwórz obiekt klasy Book, ustaw jego pola za pomocą konstruktora, a następnie wywołaj metodę PrintDetails(), aby wyświetlić dane książki

Zadanie 2

Stwórz klasę MathConstants, która zawiera stałą PI, reprezentującą stałą matematyczną. Ustaw jej wartość na 3.14159. Pokaż, że nie można zmienić tej wartości w czasie działania programu. W metodzie Main spróbuj wypisać wartość stałej PI, a następnie spróbuj zmienić jej wartość (sprawdzisz, że nie jest to możliwe w C#).

Zadanie 3

Stwórz klasę Person, która przechowuje dane o osobie:

W metodzie Main stwórz obiekt Person, przypisz wartość do właściwości Age i spróbuj ustawić wartość ujemną, aby sprawdzić, czy jest to zablokowane.

Zadanie 4

Stwórz klasę Calculator, która ma metodę Add, dodającą dwie liczby i zwracającą wynik. Dodaj metodę Multiply, która zwróci wynik mnożenia dwóch liczb. W metodzie Main stwórz obiekt Calculator i wywołaj metodę Add oraz Multiply z różnymi parametrami, wypisując wyniki na ekranie.

Zadanie 5

Stwórz klasę BankAccount, która ma zdarzenie BalanceChanged wywoływane po każdej zmianie salda konta. Wypisz komunikat, gdy saldo zostanie zmienione. W metodzie Main stwórz obiekt BankAccount i subskrybuj zdarzenie. Następnie zmień saldo konta, aby sprawdzić, czy zdarzenie jest wywoływane.

Zdarzenia

Zadanie 6

Stwórz klasę Point, reprezentującą punkt na płaszczyźnie 2D, który zawiera pola x i y. Przeciąż operator +, aby umożliwić dodawanie dwóch punktów (dodawanie ich współrzędnych). W metodzie Main stwórz dwa obiekty klasy Point i dodaj je za pomocą operatora +. Wypisz wyniki na konsoli.

Przeciążanie operatorów

Zadanie 7

Stwórz klasę StudentGrades, która przechowuje oceny ucznia w tablicy. Umożliwiaj indeksowanie ocen za pomocą numeru indeksu studenta (indeksy zaczynają się od 0). W metodzie Main stwórz obiekt StudentGrades, przypisz oceny do poszczególnych indeksów, a następnie wypisz je, używając indeksowania.

Zadanie 8

Stwórz klasę Car, która ma konstruktor przyjmujący parametry brand, model, year (marka, model, rok produkcji). W metodzie Main stwórz nowy obiekt samochodu. W metodzie Main stwórz obiekt klasy Car i sprawdź, czy poprawnie zostały przypisane wartości do pól brand, model i year.

Zadanie 9

Stwórz klasę FileManager, która otwiera plik. Zaimplementuj finalizator, który zamyka plik, gdy obiekt jest usuwany. W metodzie Main stwórz obiekt FileManager, który otwiera plik, a potem umożliwia obserwację działania finalizatora (np. przez wypisanie komunikatu, gdy plik jest zamykany).

Otwarcie pliku

Zadanie 10

Stwórz klasę Library, która ma wewnętrzną klasę Book. Klasę Book wykorzystaj do przechowywania informacji o książkach. W metodzie Main stwórz obiekt klasy Library oraz obiekt klasy Book wewnątrz tej klasy. Przypisz dane książki i wypisz je na ekranie.

Zadanie 11

Stwórz klasę Settings, która będzie przechowywać ustawienia aplikacji. Klasa powinna mieć dwa pola: Theme (typ string), które przechowuje wybrany motyw aplikacji oraz Volume (typ int), które przechowuje poziom głośności. Zaimplementuj wzorzec Singleton, aby tylko jedna instancja tej klasy była tworzona i używana w całym programie. Zadbaj o to, by dostęp do instancji odbywał się za pomocą metody GetInstance(). Dodatkowo, stwórz metodę ShowSettings(), która wypisuje aktualne ustawienia (motyw i głośność).

Zadanie 12

Stwórz klasę InstanceCounter, która będzie liczyć liczbę stworzonych instancji tej klasy. Każda nowa instancja powinna zwiększać licznik o 1. Klasa powinna mieć:

Zadanie 13

Stwórz strukturę i rekord w C# do reprezentowania danych osobowych. Każda struktura lub rekord powinien zawierać następujące pola:

Wymagania:

Instrukcja do przetestowania:

Oczekiwane rezultaty: