Klasy i obiekty
Klasy i obiekty są kluczowymi koncepcjami w programowaniu obiektowym, a C# jest językiem programowania obiektowego, co oznacza, że opiera się na tych właśnie koncepcjach. Pozwalają na tworzenie struktur i organizację kodu w sposób bardziej zrozumiały, zarządzalny i elastyczny.
Oto szczegółowy opis klas i obiektów w C#:
1 Klasy
Definicja Klasy:
- Klasa w języku C# to szablon lub plan, na podstawie którego tworzone są obiekty. Reprezentuje abstrakcję pewnego rodzaju jednostki w systemie.
- Klasy są fundamentem programowania obiektowego, pozwalają na strukturyzację i organizację kodu.
- Klasa definiuje cechy (pola), zachowanie (metody) oraz funkcjonalności obiektu.
Struktura Klasy:
- Pola (Prywatne Dane): Pola przechowują dane charakteryzujące obiekt klasy. Są to zmienne, które należą do konkretnej instancji klasy.
- Właściwości: Właściwości pozwalają na kontrolowany dostęp do pól klasy i umożliwiają walidację danych przed zapisem lub odczytem.
- Metody (Funkcje): Metody to funkcje, które mogą operować na polach klasy i wykonywać różne operacje. Metody definiują zachowania obiektu.
- Konstruktory: Konstruktor to specjalna metoda wywoływana podczas tworzenia nowej instancji klasy. Służy do inicjalizacji obiektu.
- Indeksery: Indeksery pozwalają na dostęp do elementów obiektu za pomocą indeksu, podobnie jak w przypadku tablic. Wartość indeksu może być dowolnym typem, a nie tylko typem liczbowym.
- Zdarzenia: Zdarzenia umożliwiają subskrypcję i obsługę zdarzeń wywoływanych w klasie.
- Statyczność: Członkowie statyczni (metody, pola) należą do klasy, a nie do konkretnej instancji. Są dostępne bez konieczności tworzenia obiektu danej klasy.
przykład
public class Narzedzie
{
public static int LiczbaNarzedzi;
}
Definicja Klasy przykład
public class Samochod
{
// Członkowie klasy (pola, właściwości, metody)
}
Pola (Fields) i Właściwości (Properties)
public class Samochod
{
public string Marka; // Pole
private int _rokProdukcji; // Pole prywatne
public int RokProdukcji // Właściwość
{
get { return _rokProdukcji; }
set { _rokProdukcji = value > 1900 ? value : 1900; }
}
}
Metody
public class Samochod
{
public void UruchomSilnik()
{
// Logika uruchamiania silnika
}
public int ObliczWiekSamochodu()
{
return DateTime.Now.Year - RokProdukcji;
}
}
Konstruktory
public class Samochod
{
public Samochod(string marka, int rokProdukcji)
{
Marka = marka;
RokProdukcji = rokProdukcji;
}
}
Indeksery
using System;
public class Biblioteka
{
private string[] ksiazki = new string[10];
// Indekser
public string this[int indeks]
{
get
{
if (indeks >= 0 && indeks < ksiazki.Length)
{
return ksiazki[indeks];
}
else
{
return "Indeks poza zakresem";
}
}
set
{
if (indeks >= 0 && indeks < ksiazki.Length)
{
ksiazki[indeks] = value;
}
else
{
Console.WriteLine("Indeks poza zakresem");
}
}
}
}
class Program
{
static void Main()
{
Biblioteka mojaBiblioteka = new Biblioteka();
// Ustawienie wartości za pomocą indeksera
mojaBiblioteka[0] = "Asp.Net Core";
mojaBiblioteka[1] = "Csharp";
mojaBiblioteka[2] = "Xml";
// Pobranie wartości za pomocą indeksera
Console.WriteLine(mojaBiblioteka[1]); // Wyświetli: Asp.Net Core
Console.WriteLine(mojaBiblioteka[5]); // Wyświetli: Indeks poza zakresem
}
}
Zdarzenia
using System;
// Klasa reprezentująca nadawcę zdarzenia
public class Nadawca
{
// Definicja delegata zdarzenia
public delegate void MojDelegat(string wiadomosc);
// Zdarzenie oparte na delegacie
public event MojDelegat MojEvent;
// Metoda, która wywołuje zdarzenie
public void WywolajZdarzenie(string wiadomosc)
{
Console.WriteLine("Nadawca: Wysyłam wiadomość - " + wiadomosc);
// Sprawdzamy, czy zdarzenie ma zarejestrowane jakieś obszary nasłuchujące
if (MojEvent != null)
{
// Wywołujemy zdarzenie, przekazując wiadomość
MojEvent(wiadomosc);
}
}
}
// Klasa reprezentująca odbiorcę zdarzenia
public class Odbiorca
{
// Metoda obsługująca zdarzenie
public void ObslugaZdarzenia(string wiadomosc)
{
Console.WriteLine("Odbiorca: Otrzymałem wiadomość - " + wiadomosc);
}
}
class Program
{
static void Main()
{
// Tworzymy obiekty nadawcy i odbiorcy
Nadawca mojNadawca = new Nadawca();
Odbiorca mojOdbiorca = new Odbiorca();
// Rejestrujemy metodę ObslugaZdarzenia jako obszar nasłuchujący zdarzenia w nadawcy
mojNadawca.MojEvent += mojOdbiorca.ObslugaZdarzenia;
// Wywołujemy zdarzenie
mojNadawca.WywolajZdarzenie("Nowa wiadomość!");
}
}
Oczekiwany wynik:
Nadawca: Wysyłam wiadomość - Nowa wiadomość!
Odbiorca: Otrzymałem wiadomość - Nowa wiadomość!
2 Obiekty
Definicja
- Obiekt to konkretna instancja klasy.
- Możemy utworzyć wiele obiektów na podstawie jednej klasy.
- Każdy obiekt ma swoje unikalne atrybuty (wartości pól) i może wywoływać metody klasy.
- Tworzenie obiektu polega na alokowaniu pamięci i inicjalizowaniu jego pól i właściwości zgodnie z definicją klasy.
- Obiekty są niezależne i mogą mieć różne stany.
Tworzenie Obiektu:
- new – obiekt jest tworzony za pomocą operatora
new
wraz z wywołaniem konstruktora klasy.
Przykład:Samochod mojSamochod = new Samochod("Toyota", "Camry", 2022);
- Referencje: Obiekty są przekazywane przez referencje. Oznacza to, że przypisanie jednego obiektu do innego nie tworzy nowego obiektu, ale kopiuje referencję do tego samego obiektu w pamięci.
Dostęp do Członków Klasy:
- Pola i metody klasy mogą być dostępne z poziomu obiektu, a dostęp do nich odbywa się za pomocą operatora
.
. - Przykład:
mojSamochod.PokazInformacje();
Osoba osoba1 = new Osoba("Jan", "Kowalski");
Osoba osoba2 = new Osoba("Anna", "Nowak");
osoba1.PrzedstawSie(); // "Jestem Jan Kowalski."
osoba2.PrzedstawSie(); // "Jestem Anna Nowak."
3 Filary Programowania obiektowego
Encapsulation (Enkapsulacja):
Enkapsulacja to zasada programowania obiektowego, która polega na ukrywaniu wewnętrznych detali implementacyjnych klasy i ujawnianiu tylko potrzebnych interfejsów.
Dostęp do pól i metod jest kontrolowany przez mechanizmy takie jak modyfikatory dostępu ( public
, private
, protected
, internal
, protected internal
) które określają, które elementy klasy są widoczne dla innych części kodu. Co pomaga w zabezpieczaniu i izolowaniu wewnętrznych szczegółów implementacji.
Enkapsulacja pomaga w utrzymaniu spójności danych i umożliwia łatwiejsze utrzymanie kodu, ponieważ zmiany w implementacji mogą być ukrywane przed innymi częściami programu.
public class Samochod
{
// private - dostęp tylko w obrębie klasy
private int _rokProdukcji;
// public - dostęp z dowolnego miejsca
public int RokProdukcji
{
get { return _rokProdukcji; }
set { _rokProdukcji = value > 1900 ? value : 1900; }
}
}
Dziedziczenie
Dziedziczenie to mechanizm, który pozwala na tworzenie nowych klas na podstawie istniejących klas. Dziedziczenie umożliwia jednej klasie (klasie pochodnej) korzystanie z cech i zachowań innej klasy (klasy bazowej). Dziedziczenie pozwala na ponowne wykorzystanie kodu i tworzenie hierarchii klas, co ułatwia organizację kodu i wprowadzanie zmian w jednym miejscu.
public class Zwierze
{
public void Oddychaj()
{
Console.WriteLine("Zwierze oddycha.");
}
}
public class Ptak : Zwierze
{
public void Lataj()
{
Console.WriteLine("Ptak lata.");
}
}
Ptak dziedziczy metodę Oddychaj
od klasy Zwierze.
Polimorfizm
Polimorfizm to koncepcja, która pozwala na traktowanie różnych obiektów w jednolity sposób. Możesz używać obiektów klas pochodnych jako obiektów klas bazowych.
Polimorfizm umożliwia jednemu interfejsowi obsługiwać różne typy obiektów lub jednej metodzie obsługiwać różne formy danych. W C#, polimorfizm może być osiągnięty poprzez przeciążanie metod, przesłanianie metod, interfejsy i klasy abstrakcyjne.
Metody wirtualne i abstrakcyjne umożliwiają nadpisywanie funkcji w klasach pochodnych.
public class Kształt
{
public virtual void Narysuj()
{
Console.WriteLine("Rysuję kształt.");
}
}
public class Kwadrat : Kształt
{
public override void Narysuj()
{
Console.WriteLine("Rysuję kwadrat.");
}
}
public class Kolo : Kształt
{
public override void Narysuj()
{
Console.WriteLine("Rysuję koło.");
}
}
Korzystając z polimorfizmu, możemy używać wspólnego interfejsu (Kształt
) do obsługi różnych konkretnych implementacji (Kwadrat
, Kolo
).
Abstrakcja
Abstrakcja polega na ukrywaniu zbędnych detali implementacyjnych i skupianiu się na istotnych aspektach danego obiektu. Jest to proces tworzenia modelu, który koncentruje się na najważniejszych cechach i zachowaniach, ignorując mniej istotne szczegóły.
Klasy Abstrakcyjne:
Klasy abstrakcyjne są klasami, które nie mogą być instancjonowane bezpośrednio, ale mogą zawierać metody abstrakcyjne, czyli metody, które nie posiadają implementacji w klasie abstrakcyjnej. Klasy te służą jako szablony do dziedziczenia.
public abstract class Figura
{
public abstract double PolePowierzchni(); // Metoda abstrakcyjna
}
Interfejsy:
Interfejsy to zbiorcze zestawy metod, które są implementowane przez różne klasy. W odróżnieniu od klas abstrakcyjnych, interfejsy nie zawierają żadnej implementacji i umożliwiają implementację wielu interfejsów w jednej klasie.
public interface IDzwieki
{
void WydajDzwiek();
}
Właściwości Abstrakcyjne:
Właściwości abstrakcyjne pozwalają na definiowanie abstrakcyjnych pól w klasach abstrakcyjnych. Implementacja tych pól zostanie dostarczona przez klasę dziedziczącą.
public abstract class Pojazd
{
public abstract string Marka { get; set; }
}
Abstrakcja pozwala na tworzenie bardziej ogólnych i elastycznych struktur w kodzie, co z kolei ułatwia rozbudowę i modyfikację systemu. Jest to kluczowa koncepcja w programowaniu obiektowym, która pomaga w modelowaniu rzeczywistych problemów i skupianiu się na istotnych aspektach systemu.
Filary programowania obiektowego stanowią podstawę dla wielu zaawansowanych koncepcji i technik w programowaniu. Wspierają one tworzenie kodu, który jest bardziej elastyczny, łatwiejszy do zrozumienia, utrzymania i rozbudowy. Programowanie obiektowe jest kluczowym paradygmatem programowania, który jest szeroko stosowany we współczesnym oprogramowaniu.
Oto kilka dodatkowych tematów, które warto zrozumieć:
Klasy i Metody Statyczne
Klasy statyczne zawierają tylko metody statyczne i nie mogą być instancjonowane. Metody statyczne są dostępne bez konieczności tworzenia obiektu danej klasy.
public static class Narzedzia
{
public static void WykonajCos()
{
// Implementacja
}
}
Metody Rozszerzeń
Metody rozszerzeń umożliwiają dodawanie nowych metod do istniejących typów bez modyfikowania ich definicji.
public static class RozszerzeniaString
{
public static bool ZawieraSpacje(this string tekst)
{
return tekst.Contains(" ");
}
}
Metody Generyczne
Metody generyczne pozwalają na tworzenie funkcji, które mogą operować na różnych typach danych, nie tracąc typów w trakcie kompilacji.
public T ZnajdzMax<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}
Typy Generyczne
Typy generyczne umożliwiają tworzenie klas, struktur, interfejsów itp., które mogą obsługiwać różne typy danych bez utraty bezpieczeństwa typów.
public class Kolejka<T>
{
private List<T> elementy = new List<T>();
public void Dodaj(T element)
{
elementy.Add(element);
}
}
Właściwości Obliczane
Właściwości obliczane to właściwości, które nie przechowują wartości, ale obliczają ją dynamicznie w trakcie dostępu.
public class Kolo
{
public double Promien { get; set; }
public double PolePowierzchni
{
get { return Math.PI * Promien * Promien; }
}
}
yield return
Umożliwia tworzenie iteratorów, zwracając kolejne elementy z sekwencji.
public IEnumerable<int> LiczbyParzyste()
{
for (int i = 0; i < 10; i++)
{
if (i % 2 == 0)
yield return i;
}
}
unsafe
Umożliwia korzystanie z niebezpiecznych operacji w kontrolowanym środowisku.
unsafe
{
int x = 10;
int* ptr = &x;
}
Metody Wyrażeń (Expression-bodied Methods)
Skrócona składnia dla jednoliniowych metod.
public class Kalkulator
{
public int Dodaj(int a, int b) => a + b;
}
Wyrażenia Lambda
Pozwalają na tworzenie krótkich anonimowych funkcji.
Func<int, int, int> dodaj = (x, y) => x + y;
Dekonstrukcja
Dostępna od C# 7.0, umożliwia rozpakowanie obiektu na składowe.
public class Osoba
{
public string Imie { get; set; }
public string Nazwisko { get; set; }
public void Deconstruct(out string imie, out string nazwisko)
{
imie = Imie;
nazwisko = Nazwisko;
}
}
// Użycie
var osoba = new Osoba { Imie = "Jan", Nazwisko = "Kowalski" };
var (imię, nazwisko) = osoba;
Rekurencyjne Wyrażenia (Expression Trees)
Expression Trees pozwalają na reprezentację drzewa wyrażeń w postaci obiektowej. Jest to często używane w przypadku tworzenia dynamicznych zapytań LINQ.
Expression<Func<int, bool>> isEvenExpr = x => x % 2 == 0;
Atrybuty
Atrybuty pozwalają na dodawanie metadanych do kodu. Są używane do dostarczania informacji o kodzie dla kompilatora lub środowiska wykonawczego.
[Serializable]
public class MojaKlasa { }
Serializacja i Deserializacja
Serializacja to proces przekształcania obiektu w formę, która może być łatwo zapisana, na przykład do pliku. Deserializacja to proces odtwarzania obiektu z tej zapisanej formy.
var json = JsonSerializer.Serialize(mojObiekt);
var odtworzonyObiekt = JsonSerializer.Deserialize<MojTyp>(json);
Dispose Pattern (IDisposable)
Jeśli klasa używa zasobów nie zarządzanych (takich jak pliki, połączenia z bazą danych), może implementować interfejs IDisposable
do zwalniania tych zasobów.
public class KlasaZasobow : IDisposable
{
// Implementacja IDisposable
public void Dispose()
{
// Zwolnienie zasobów
}
}
Wstrzykiwanie Zależności
Wstrzykiwanie zależności to technika, w której obiekty otrzymują swoje zależności z zewnątrz, zamiast tworzyć je samodzielnie. To umożliwia bardziej elastyczne zarządzanie zależnościami.
public class Samochod
{
private Silnik silnik;
public Samochod(Silnik silnik)
{
this.silnik = silnik;
}
}
Refleksja
Refleksja umożliwia analizowanie i manipulowanie metadanych programu w trakcie jego wykonywania. Można uzyskiwać informacje o typach, metodach, polach itp.
Type typ = typeof(MojaKlasa);
MethodInfo metoda = typ.GetMethod("MojaMetoda");
Asynchroniczność
W języku C# można używać słowa kluczowego async
i await
do tworzenia asynchronicznych metod, które pozwalają na efektywne zarządzanie operacjami oczekującymi na wyniki.
public async Task<int> PobierzDaneAsync()
{
// Operacje asynchroniczne
return await Task.FromResult(42);
}
Dynamiczne Typy
Dynamiczne typy w C# pozwalają na omijanie sprawdzania typów w czasie kompilacji, co umożliwia bardziej elastyczne operacje na danych.
dynamic dynamicznyObiekt = 42;
dynamic wynik = dynamicznyObiekt + "Tekst";
CQRS
CQRS to wzorzec projektowy, który podziela system na dwie części – jedną odpowiedzialną za obsługę zapytań (query), a drugą za obsługę poleceń (command).
// Przykład implementacji CQRS
public class ZapytanieHandler : IRequestHandler<Zapytanie, Wynik>
{
public Task<Wynik> Handle(Zapytanie request, CancellationToken cancellationToken)
{
// Obsługa zapytania
}
}
public class PolecenieHandler : IRequestHandler<Polecenie, Unit>
{
public Task<Unit> Handle(Polecenie request, CancellationToken cancellationToken)
{
// Obsługa polecenia
}
}
Wzorce Projektowe
Stosowanie różnych wzorców projektowych, takich jak Singleton, Fabryka, Strategia, Obserwator, itp., w celu rozwiązania konkretnych problemów projektowych.
Przykład zastosowania wzorca Strategia
using System;
// Interfejs definiujący operacje strategii
public interface IStrategia
{
void WykonajAkcje();
}
// Implementacje różnych strategii
public class StrategiaA : IStrategia
{
public void WykonajAkcje()
{
Console.WriteLine("Wykonuję akcję zgodnie z strategią A");
}
}
public class StrategiaB : IStrategia
{
public void WykonajAkcje()
{
Console.WriteLine("Wykonuję akcję zgodnie z strategią B");
}
}
public class StrategiaC : IStrategia
{
public void WykonajAkcje()
{
Console.WriteLine("Wykonuję akcję zgodnie z strategią C");
}
}
// Klasa, która używa strategii
public class Klient
{
private IStrategia strategia;
// Konstruktor, umożliwiający ustawienie konkretnej strategii
public Klient(IStrategia wybranaStrategia)
{
this.strategia = wybranaStrategia;
}
// Metoda wywołująca operację zgodnie z aktualnie wybraną strategią
public void WykonajAkcjeZgodnieZeStrategia()
{
strategia.WykonajAkcje();
}
// Metoda umożliwiająca zmianę strategii w trakcie działania programu
public void UstawStrategie(IStrategia nowaStrategia)
{
this.strategia = nowaStrategia;
}
}
class Program
{
static void Main()
{
// Tworzymy klienta i przekazujemy mu strategię A
var klient = new Klient(new StrategiaA());
// Klient wykonuje akcję zgodnie z aktualną strategią
klient.WykonajAkcjeZgodnieZeStrategia();
// Zmieniamy strategię klientowi na strategię B
klient.UstawStrategie(new StrategiaB());
// Klient ponownie wykonuje akcję, tym razem zgodnie z nową strategią
klient.WykonajAkcjeZgodnieZeStrategia();
}
}
W tym przykładzie wzorzec Strategia jest używany do odseparowania algorytmów od klienta. Klient może dynamicznie zmieniać strategię w trakcie działania programu, co umożliwia elastyczne dostosowywanie zachowań bez konieczności modyfikowania samego klienta. każda strategia implementuje wspólny interfejs IStrategia
, co umożliwia zastępowanie jednej strategii drugą bez wprowadzania zmian w kodzie klienta.
To są tylko niektóre z zaawansowanych tematów związanych z klasami i obiektami w języku C#. Rozumienie tych zaawansowanych tematów pozwala na bardziej zaawansowane i elastyczne korzystanie z klas i obiektów w języku C#.
Całość jest dobrze zorganizowana i prezentuje kluczowe koncepcje programowania obiektowego w C#.
Przykłady są zrozumiałe, a komentarze dodają wartości, wyjaśniając koncepcje i podając przykłady zastosowań.
Klarowne przedstawienie filarów programowania obiektowego, a także wartościowa lista dodatkowych tematów do zrozumienia.
Dodatkowe tematy, takie jak Klasy i Metody Statyczne, Metody Generyczne, czy Metody Rozszerzeń, są dobrze ujęte.
Dobrze wytłumaczona koncepcja abstrakcji i różnice między klasami abstrakcyjnymi a interfejsami.
Przykłady klas abstrakcyjnych i interfejsów są zrozumiałe.
asne wyjaśnienie, co to jest obiekt i jak tworzyć instancje klas.
Przekazanie informacji o referencjach w kontekście przypisywania obiektu do innego obiektu jest pomocne.
Przykład z zdarzeniem jest świetny, pokazuje jak działa mechanizm subskrypcji i obsługi zdarzeń w C#.
Dobrze, że zwrócono uwagę na sprawdzenie, czy zdarzenie ma zarejestrowane obszary nasłuchujące przed jego wywołaniem.
Wyjaśnienie pól i właściwości jest zrozumiałe. Dobrze pokazane różnice między publicznymi polami a prywatnymi polami z właściwościami.
Właściwość RokProdukcji zastosowująca walidację to dobry sposób na kontrolę danych.