Skip to content

Zadania (31-40)

Czym są kolekcje dostępne w C#, podać dwa przykłady korzystania

Section titled “Czym są kolekcje dostępne w C#, podać dwa przykłady korzystania”

Kolekcje to klasy służące do przechowywania i zarządzania grupami obiektów. W odróżnieniu od tablic mają zmienny rozmiar (mogą rosnąć i maleć w trakcie działania programu) oraz dostarczają gotowych metod do dodawania, usuwania i wyszukiwania elementów.

Kolekcje dzielą się na dwie grupy. Niegeneryczne (z przestrzeni System.Collections, np. ArrayList, Hashtable) przechowują elementy jako typ object, przez co wymagają rzutowania i powodują pakowanie (boxing) typów wartościowych. Generyczne (z System.Collections.Generic) są typowane, czyli przechowują elementy konkretnego typu T, dzięki czemu są bezpieczne typowo i wydajniejsze. Dziś używa się prawie wyłącznie generycznych.

Wszystkie kolekcje opierają się na wspólnych interfejsach: IEnumerable (umożliwia foreach), ICollection (dodaje Count, Add, Remove), IList (dostęp po indeksie) oraz IDictionary (dostęp po kluczu).

Do najpopularniejszych kolekcji generycznych należą:

  • List - dynamiczna lista indeksowana.
  • Dictionary<TKey, TValue> - pary klucz-wartość, szybki dostęp po kluczu.
  • Queue - kolejka FIFO (pierwszy wszedł, pierwszy wychodzi).
  • Stack - stos LIFO (ostatni wszedł, pierwszy wychodzi).
  • HashSet - zbiór unikalnych elementów (bez duplikatów).
using System.Collections.Generic;
// List<T> - dynamiczna lista elementów tego samego typu
List<string> names = new List<string>();
// dodanie elementów na koniec listy
names.Add("Ala");
names.Add("Ola");
// odczyt po indeksie
Console.WriteLine(names[0]);
// usuwanie i sprawdzanie zawartości
names.Remove("Ala");
bool hasOla = names.Contains("Ola");
// Dictionary<TKey, TValue> - kolekcja par klucz-wartość
Dictionary<string, int> ages = new Dictionary<string, int>();
// dodanie pary klucz-wartość
ages["Ala"] = 20;
ages.Add("Ola", 25);
// odczyt wartości po kluczu
Console.WriteLine(ages["Ala"]);
// bezpieczny odczyt, gdy klucz może nie istnieć
if (ages.TryGetValue("Ola", out int age))
Console.WriteLine(age);

Pozostałe trzy kolekcje na przykładach:

// Queue<T> - kolejka FIFO
Queue<string> queue = new Queue<string>();
// dodanie na koniec kolejki
queue.Enqueue("pierwszy");
queue.Enqueue("drugi");
// pobranie i usunięcie z początku (zwraca "pierwszy")
string first = queue.Dequeue();
// Stack<T> - stos LIFO
Stack<int> stack = new Stack<int>();
// odłożenie na wierzch stosu
stack.Push(1);
stack.Push(2);
// zdjęcie z wierzchu (zwraca 2)
int top = stack.Pop();
// HashSet<T> - zbiór unikalnych elementów
HashSet<int> set = new HashSet<int>();
set.Add(1);
// druga próba dodania 1 zostanie zignorowana (brak duplikatów)
set.Add(1);
// w zbiorze jest tylko jeden element
Console.WriteLine(set.Count);

Skrót do zapamiętania: Kolekcje to klasy do przechowywania grup obiektów o zmiennym rozmiarze. Generyczne (System.Collections.Generic) są typowane i wydajne, niegeneryczne (System.Collections) trzymają object. Przykłady: List (lista), Dictionary (klucz-wartość), Queue (FIFO), Stack (LIFO), HashSet (unikalne).

  • Zmienny rozmiar i gotowe metody (Add, Remove, Contains).
  • Niegeneryczne (System.Collections, object) vs generyczne (System.Collections.Generic, typowane).
  • Interfejsy: IEnumerable (foreach), ICollection, IList, IDictionary.
  • List - lista indeksowana; Dictionary<TKey,TValue> - pary klucz-wartość.
  • Queue (FIFO: Enqueue/Dequeue), Stack (LIFO: Push/Pop), HashSet (unikalne).

LINQ (Language Integrated Query) to mechanizm wbudowany w język (C#, VB.NET) pozwalający odpytywać różne źródła danych w jednolity sposób, bezpośrednio w kodzie. Zamiast osobnych języków zapytań dla różnych źródeł, używamy tej samej składni do kolekcji, baz danych, plików XML czy obiektów.

LINQ udostępnia dwie równoważne składnie: zapytaniową (przypominającą SQL, ze słowami from, where, select) oraz metodową (płynną, z metodami rozszerzającymi jak Where, Select, OrderBy łączonymi przez kropkę). Kompilator i tak tłumaczy składnię zapytaniową na metodową. W zależności od źródła danych wyróżnia się odmiany: LINQ to Objects (kolekcje), LINQ to SQL / LINQ to Entities (bazy), LINQ to XML.

Najważniejsze operatory to: where (filtrowanie), select (rzutowanie/wybór pól), orderby (sortowanie), group by (grupowanie), join (łączenie kolekcji) oraz operatory agregujące i zwracające pojedynczy wynik (Count, Sum, Average, First, Any).

using System.Linq;
using System.Collections.Generic;
List<int> numbers = new List<int> { 5, 2, 8, 1, 4 };
// składnia zapytaniowa: filtr + sortowanie + wybór
var query =
from n in numbers
where n > 2
orderby n
select n;
// dokładnie ta sama operacja w składni metodowej
var fluent = numbers
.Where(n => n > 2)
.OrderBy(n => n)
.Select(n => n);

Ważną cechą LINQ jest leniwe wykonanie (deferred execution): zapytanie nie liczy się w momencie zdefiniowania, lecz dopiero gdy zaczynamy iterować po wyniku (foreach) albo wymusimy obliczenie metodą jak ToList(), Count() czy First().

// operatory agregujące zwracają pojedynczą wartość (wymuszają wykonanie)
int count = numbers.Count(n => n > 2);
int sum = numbers.Sum();
bool anyBig = numbers.Any(n => n > 7);
// materializacja wyniku do listy
List<int> evens = numbers.Where(n => n % 2 == 0).ToList();

Na obiektach złożonych często łączy się grupowanie i rzutowanie do nowego, anonimowego typu:

// grupowanie liczb na parzyste i nieparzyste
var groups =
from n in numbers
group n by n % 2 == 0 into g
select new { Even = g.Key, Count = g.Count() };

Skrót do zapamiętania: LINQ to jednolity sposób odpytywania różnych źródeł danych (kolekcje, bazy, XML) wprost w C#. Dwie składnie: zapytaniowa (from/where/select) i metodowa (Where/Select), kompilator i tak zamienia jedną na drugą. Leniwe wykonanie (deferred execution). Odmiany: to Objects, to SQL/Entities, to XML.

  • Jednolite zapytania do wielu źródeł danych w jednym języku.
  • Składnia zapytaniowa (from…where…select) i metodowa (Where().Select()).
  • Operatory: where, select, orderby, group by, join; agregujące: Count, Sum, Average, First, Any.
  • Leniwe wykonanie - liczy się dopiero przy iteracji lub ToList()/Count().
  • Odmiany: LINQ to Objects, to SQL/Entities, to XML.

Podać przykład zapytania LINQ do obiektu

Section titled “Podać przykład zapytania LINQ do obiektu”

LINQ to Objects to odmiana LINQ działająca na kolekcjach przechowywanych w pamięci (implementujących IEnumerable), np. tablicach czy listach. Poniższy przykład wybiera z tablicy liczby parzyste i sortuje je malejąco, pokazując obie składnie.

using System.Linq;
// kolekcja źródłowa w pamięci
int[] numbers = { 1, 2, 3, 4, 5, 6 };
// składnia zapytaniowa: wybierz liczby parzyste i posortuj malejąco
var query =
from n in numbers
where n % 2 == 0
orderby n descending
select n;
// ta sama operacja w składni metodowej
var result = numbers.Where(n => n % 2 == 0).OrderByDescending(n => n);
// wypisanie wyników zapytania
foreach (int n in query)
Console.WriteLine(n);

Skrót do zapamiętania: LINQ to Objects działa na kolekcjach w pamięci (IEnumerable). Przykład: from n in numbers where n % 2 == 0 orderby n descending select n lub metodowo numbers.Where(...).OrderByDescending(...).

  • Działa na kolekcjach w pamięci (tablice, listy - IEnumerable).
  • Zapytaniowo: from / where / orderby / select.
  • Metodowo: .Where(n => ...).OrderByDescending(...).
  • Wynik to też kolekcja (leniwie obliczana).

Przykład zapytania SQL w modelu bezpołączeniowym

Section titled “Przykład zapytania SQL w modelu bezpołączeniowym”

W modelu bezpołączeniowym dane pobiera się z bazy do obiektu DataSet (lokalnej kopii w pamięci) za pomocą DataAdapter. Adapter sam otwiera połączenie, wykonuje zapytanie i zamyka je, więc pracujemy na danych offline, bez utrzymywania otwartego połączenia.

using System.Data;
using System.Data.SqlClient;
// połączenie z bazą danych
using (SqlConnection connection = new SqlConnection("...connection string..."))
{
// adapter z zapytaniem SQL - pomost między bazą a DataSet
SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM Users", connection);
// lokalny zbiór danych przechowywany w pamięci
DataSet dataSet = new DataSet();
// Fill otwiera połączenie, pobiera dane i zamyka połączenie
adapter.Fill(dataSet, "Users");
// praca na danych w pamięci, bez otwartego połączenia
foreach (DataRow row in dataSet.Tables["Users"].Rows)
{
Console.WriteLine(row["Name"]);
}
}

Skrót do zapamiętania: Model bezpołączeniowy: SqlDataAdapter wykonuje SELECT i metodą Fill ładuje dane do DataSet (kopia w pamięci); połączenie otwierane tylko na czas pobrania. Dalej pracujemy offline na DataTable/DataRow.

  • Obiekty: SqlConnection, SqlDataAdapter, DataSet.
  • adapter.Fill(dataSet, "Users") pobiera dane i zamyka połączenie.
  • Praca na DataSet.Tables[…].Rows offline.
  • Zmiany można później zsynchronizować przez adapter (Update).

Entity Framework (EF) to technologia mapowania obiektowo-relacyjnego (ORM) firmy Microsoft. Automatycznie odwzorowuje tabele bazy danych na klasy .NET (encje), a wiersze na obiekty. Dzięki temu programista pracuje z danymi jak ze zwykłymi obiektami, a EF generuje za niego zapytania SQL, co ogranicza ilość ręcznie pisanego kodu dostępu do danych.

EF udostępnia trzy podejścia do tworzenia modelu: Code First (najpierw klasy, z nich generowana jest baza), Database First (najpierw istniejąca baza, z niej generowane są klasy) oraz Model First. Centralnym obiektem jest DbContext (reprezentuje sesję z bazą), a tabele udostępniane są jako DbSet. Zapytania pisze się w LINQ (LINQ to Entities).

using System.Linq;
using Microsoft.EntityFrameworkCore;
// encja - klasa odwzorowana na tabelę w bazie
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
// DbContext - sesja z bazą; każdy DbSet to jedna tabela
public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }
}
// użycie: praca na danych jak na zwykłych obiektach
using (AppDbContext db = new AppDbContext())
{
// dodanie nowego wiersza (INSERT generowany przez EF)
db.Users.Add(new User { Name = "Ala" });
// zapis zmian do bazy
db.SaveChanges();
// zapytanie w LINQ to Entities (EF tłumaczy je na SQL SELECT)
var adults = db.Users.Where(u => u.Name.StartsWith("A")).ToList();
}

Skrót do zapamiętania: Entity Framework to ORM Microsoftu: mapuje tabele na klasy (encje), a wiersze na obiekty, i sam generuje SQL. Podejścia: Code First, Database First, Model First. DbContext + DbSet, zapytania w LINQ.

  • ORM: mapuje tabele -> klasy (encje), wiersze -> obiekty.
  • Sam generuje SQL, mniej ręcznego kodu dostępu do danych.
  • Podejścia: Code First, Database First, Model First.
  • DbContext (sesja z bazą), DbSet (tabela), LINQ to Entities.

Zalety i wady podejścia DOM w analizie plików XML

Section titled “Zalety i wady podejścia DOM w analizie plików XML”

DOM (Document Object Model) to sposób przetwarzania XML polegający na wczytaniu całego dokumentu do pamięci i zbudowaniu z niego drzewa obiektów. Po wczytaniu mamy pełny, swobodny dostęp do wszystkich węzłów: możemy nawigować w dowolnym kierunku, odczytywać, dodawać, modyfikować i usuwać elementy.

Zaletą jest wygoda i pełna kontrola: łatwa nawigacja w obie strony oraz możliwość modyfikacji dokumentu. Wadą jest duże zużycie pamięci, ponieważ cały dokument musi się zmieścić w RAM, co przy bardzo dużych plikach bywa nieefektywne i wolne. Alternatywą dla dużych plików jest podejście strumieniowe (np. SAX lub XmlReader), które czyta dokument sekwencyjnie i zużywa mniej pamięci, ale nie pozwala na swobodną nawigację.

W .NET model DOM realizuje klasa XmlDocument: metoda Load wczytuje cały plik do pamięci jako drzewo węzłów, po którym można potem swobodnie chodzić.

using System.Xml;
// wczytanie całego dokumentu do pamięci (budowa drzewa DOM)
XmlDocument doc = new XmlDocument();
doc.Load("users.xml");
// swobodna nawigacja po drzewie: wybór wszystkich węzłów <user>
XmlNodeList users = doc.GetElementsByTagName("user");
foreach (XmlNode user in users)
{
// odczyt wartości elementu potomnego i atrybutu
string name = user["name"].InnerText;
string id = user.Attributes["id"].Value;
Console.WriteLine($"{id}: {name}");
}
// modyfikacja drzewa w pamięci i zapis z powrotem do pliku
XmlElement newUser = doc.CreateElement("user");
newUser.InnerText = "Ola";
doc.DocumentElement.AppendChild(newUser);
doc.Save("users.xml");

Skrót do zapamiętania: DOM wczytuje cały XML do pamięci jako drzewo obiektów (w .NET klasa XmlDocument, metoda Load). Zalety: pełny dostęp, swobodna nawigacja, modyfikacja. Wady: duże zużycie pamięci i wolniej przy wielkich plikach (alternatywa: strumieniowy SAX/XmlReader).

  • DOM = cały dokument w pamięci jako drzewo obiektów.
  • Zalety: pełny dostęp, nawigacja w obie strony, łatwa modyfikacja.
  • Wady: duże zużycie pamięci, wolny dla wielkich plików.
  • Alternatywa: podejście strumieniowe (SAX/XmlReader) - mało pamięci, brak swobodnej nawigacji.

Idea architektury MVC i jej zalety względem ASP.NET

Section titled “Idea architektury MVC i jej zalety względem ASP.NET”

MVC (Model-View-Controller) to wzorzec architektoniczny dzielący aplikację na trzy współpracujące części o jasnych rolach. Model odpowiada za dane i logikę biznesową. View (widok) odpowiada za prezentację, czyli wygenerowanie interfejsu. Controller (kontroler) przyjmuje żądania użytkownika, korzysta z modelu i wybiera widok do wyświetlenia. Dzięki temu logika, dane i prezentacja są od siebie oddzielone.

W porównaniu z klasycznym ASP.NET Web Forms, ASP.NET MVC daje lepszą separację odpowiedzialności i łatwiejszą testowalność (logikę kontrolerów testuje się bez interfejsu). Zapewnia też pełną kontrolę nad generowanym kodem HTML, rezygnuje z ciężkiego ViewState, wspiera przyjazne adresy URL (routing) i lepiej pasuje do nowoczesnych aplikacji webowych.

Poniższy szkielet pokazuje wszystkie trzy części: model z danymi, kontroler obsługujący żądanie i wybierający widok oraz widok prezentujący przekazany model.

// Model - dane i logika biznesowa
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
// Controller - obsługuje żądanie, pobiera model i wskazuje widok
public class ProductController : Controller
{
public ActionResult Details(int id)
{
// pobranie danych (model)
Product product = new Product { Id = id, Name = "Laptop" };
// przekazanie modelu do widoku
return View(product);
}
}
@* View (Razor) - tylko prezentacja przekazanego modelu *@
@model Product
<h1>@Model.Name</h1>
<p>Id: @Model.Id</p>

Skrót do zapamiętania: MVC dzieli aplikację na Model (dane/logika), View (prezentacja) i Controller (obsługa żądań, łączy model z widokiem). Względem Web Forms: lepsza separacja, testowalność, pełna kontrola nad HTML, brak ViewState, przyjazne URL.

  • Model - dane i logika; View - prezentacja; Controller - obsługa żądań.
  • Kontroler łączy model z widokiem, oddziela logikę od prezentacji.
  • Zalety vs Web Forms: separacja odpowiedzialności, lepsza testowalność.
  • Pełna kontrola nad HTML, brak ViewState, routing (przyjazne URL).

Cykl życia strony to sekwencja etapów, przez które przechodzi strona Web Forms przy każdym żądaniu, od jego odebrania aż po odesłanie odpowiedzi i zwolnienie strony. W każdym etapie wywoływane są odpowiednie metody i zdarzenia, w których można umieścić własny kod.

Główne etapy to: odebranie żądania, inicjalizacja strony i kontrolek (Init), wczytanie stanu i danych (Load, z odtworzeniem ViewState), obsługa zdarzeń wywołanych przez postback (np. kliknięcia), faza przygotowania do renderowania (PreRender), renderowanie strony do HTML (Render) oraz zwolnienie zasobów (Unload). Właściwość IsPostBack pozwala odróżnić pierwsze załadowanie strony od ponownego wysłania formularza.

W code-behind do kolejnych etapów wpinamy się, nadpisując ich metody (OnInit, OnPreRender, OnUnload) lub obsługując zdarzenia (Page_Load). Najczęściej używa się Page_Load z warunkiem IsPostBack, żeby kod inicjalizujący wykonał się tylko przy pierwszym wejściu, a nie przy każdym postbacku.

public partial class Default : Page
{
// etap Init - nadpisujemy metodę, by wpiąć się w dany etap
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
}
// etap Load - wykonywany przy każdym żądaniu
protected void Page_Load(object sender, EventArgs e)
{
// tylko przy pierwszym załadowaniu, nie przy postbacku
if (!IsPostBack)
{
// np. wypełnienie listy danymi
}
}
}

Skrót do zapamiętania: Etapy strony Web Forms przy każdym żądaniu: Init -> Load (z ViewState) -> obsługa zdarzeń postback -> PreRender -> Render (do HTML) -> Unload. IsPostBack odróżnia pierwsze załadowanie od postbacku.

  • Sekwencja etapów wykonywana przy każdym żądaniu strony.
  • Init: inicjalizacja strony i kontrolek.
  • Load: wczytanie stanu (ViewState) i danych.
  • Obsługa zdarzeń postback (kliknięcia itp.).
  • PreRender -> Render (HTML) -> Unload (zwolnienie).
  • IsPostBack: pierwsze załadowanie vs ponowne wysłanie.

SOA (Service-Oriented Architecture, architektura zorientowana na usługi) to styl budowy systemów, w którym aplikacja składa się z luźno powiązanych, niezależnych usług. Każda usługa realizuje określoną funkcję biznesową i udostępnia ją przez dobrze zdefiniowany kontrakt (interfejs), a usługi komunikują się ze sobą za pomocą standardowych protokołów i komunikatów (np. SOAP lub REST).

Kluczowe cechy usług to autonomiczność, niezależność od platformy i technologii oraz możliwość wielokrotnego użytku. Dzięki temu SOA zapewnia luźne powiązanie komponentów, łatwą integrację różnych systemów (interoperacyjność), ponowne wykorzystanie usług i lepszą skalowalność. Mikrousługi można traktować jako współczesne rozwinięcie tej idei.

Sercem usługi jest jej kontrakt, czyli interfejs opisujący udostępniane operacje. W .NET (technologia WCF) definiuje się go atrybutami ServiceContract i OperationContract.

using System.ServiceModel;
// kontrakt usługi - publiczny interfejs widziany przez klientów
[ServiceContract]
public interface IUserService
{
// operacja udostępniana przez usługę
[OperationContract]
string GetUserName(int id);
}
// implementacja usługi (ukryta za kontraktem)
public class UserService : IUserService
{
public string GetUserName(int id) => "Ala";
}

Skrót do zapamiętania: SOA to architektura z luźno powiązanych, niezależnych usług komunikujących się przez standardowe protokoły (SOAP/REST) i kontrakty. Cechy: autonomia, niezależność od platformy, wielokrotne użycie, interoperacyjność.

  • System złożony z luźno powiązanych, niezależnych usług.
  • Usługa = funkcja biznesowa za zdefiniowanym kontraktem (interfejsem).
  • Komunikacja przez standardowe protokoły/komunikaty (SOAP, REST).
  • Zalety: luźne powiązanie, ponowne użycie, interoperacyjność, skalowalność.

SOAP (Simple Object Access Protocol) to protokół wymiany strukturalnych komunikatów między aplikacjami, stosowany w usługach sieciowych (web services). Komunikaty mają postać dokumentów XML, dzięki czemu są niezależne od platformy i języka programowania. SOAP jest też niezależny od protokołu transportowego, choć najczęściej przesyła się go przez HTTP.

Komunikat SOAP ma ustaloną strukturę koperty (Envelope), która zawiera opcjonalny nagłówek (Header) z metadanymi (np. uwierzytelnianie) oraz obowiązkowe ciało (Body) z właściwą treścią żądania lub odpowiedzi. Interfejs usługi SOAP opisuje się zwykle dokumentem WSDL.

<?xml version="1.0"?>
<!-- koperta - element nadrzędny komunikatu SOAP -->
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<!-- nagłówek - opcjonalne metadane -->
<soap:Header></soap:Header>
<!-- ciało - właściwa treść żądania lub odpowiedzi -->
<soap:Body>
<getUser>
<id>1</id>
</getUser>
</soap:Body>
</soap:Envelope>

Skrót do zapamiętania: SOAP to protokół wymiany komunikatów XML w usługach sieciowych, niezależny od platformy i transportu (zwykle HTTP). Komunikat to koperta (Envelope) z nagłówkiem (Header) i ciałem (Body); interfejs opisuje WSDL.

  • Protokół wymiany komunikatów oparty na XML.
  • Niezależny od platformy/języka i od transportu (najczęściej HTTP).
  • Struktura: Envelope -> Header (opcjonalny) + Body (obowiązkowy).
  • Interfejs usługi opisuje WSDL.