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).
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!
}
}
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.
thisSł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).
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!
}
}
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
}
}
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.
}
}
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
}
}
Stwórz klasę
Book, która przechowuje dane o książce:
- Pole title (tytuł książki),
- Pole author (autor),
- Pole pageCount (liczba stron).
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
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łejPI, a następnie spróbuj zmienić jej wartość (sprawdzisz, że nie jest to możliwe w C#).
Stwórz klasę
Person, która przechowuje dane o osobie:
- Pole age (wiek),
- Właściwość Age, która zapewnia, że wiek nie może być ujemny.
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.
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.
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.
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.
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.
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 klasyCari sprawdź, czy poprawnie zostały przypisane wartości do pól brand, model i year.
Stwórz klasę
FileManager, która otwiera plik. Zaimplementuj finalizator, który zamyka plik, gdy obiekt jest usuwany. W metodzie Main stwórz obiektFileManager, który otwiera plik, a potem umożliwia obserwację działania finalizatora (np. przez wypisanie komunikatu, gdy plik jest zamykany).
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.
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ść).
- W metodzie Main programu stwórz dwa obiekty klasy Settings, używając metody GetInstance().
- Sprawdź, czy obie zmienne wskazują na tę samą instancję (np. porównując je za pomocą metody ReferenceEquals).
- Ustaw różne wartości dla pól Theme i Volume przy pomocy jednej instancji klasy, a następnie wywołaj metodę ShowSettings(), aby upewnić się, że wartości zostały poprawnie zapisane i są dostępne w obu obiektach.
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ć:
- Pole instanceCount (typ int), które przechowuje liczbę utworzonych instancji.
- Konstruktor, który zwiększa wartość instanceCount przy każdym utworzeniu obiektu.
- Statyczną metodę GetInstanceCount(), która zwraca aktualną liczbę stworzonych instancji.
- W metodzie Main programu utwórz kilka instancji klasy InstanceCounter.
- Po każdej instancji wywołaj metodę GetInstanceCount() i sprawdź, czy liczba instancji jest poprawnie zliczana.
- Upewnij się, że licznik nie jest resetowany po każdej instancji, a jego wartość rośnie z każdą nową instancją.
Stwórz strukturę i rekord w C# do reprezentowania danych osobowych. Każda struktura lub rekord powinien zawierać następujące pola:
- Name (typ string) - imię osoby,
- Age (typ int) - wiek osoby,
- Email (typ string) - adres e-mail osoby. Zaimplementuj dwie klasy:
- Struktura PersonStruct: Powinna zawierać te same pola, ale w strukturze, która jest typem wartości.
- Rekord PersonRecord: Powinna zawierać te same pola, ale w rekordzie, który jest typem referencyjnym.
Wymagania:
- Stwórz instancje zarówno struktury, jak i rekordu.
- Zainicjuj wartości pól w obu przypadkach.
- Wypisz dane osobowe dla obu typów (struktura i rekord).
Instrukcja do przetestowania:
- W metodzie Main stwórz instancje zarówno struktury PersonStruct, jak i rekordu PersonRecord.
- Zainicjuj pola Name, Age i Email w obu przypadkach.
- Wypisz dane tych osób na konsolę.
- Zwróć uwagę na różnicę w sposobie przechowywania danych w strukturze (typ wartości) i rekordzie (typ referencyjny).
Oczekiwane rezultaty:
- Strukturę należy traktować jako kopię wartości, więc jej zmiany po przekazaniu do funkcji nie wpłyną na oryginalny obiekt.
- Rekord powinien zachowywać się jak referencja – zmiana wartości w jednym obiekcie rekordowym powinna wpłynąć na wszystkie kopie.