Drodzy czytelnicy, poniższe pytania zostały przygotowane w taki sposób, aby każdy z Was mógł się zaznajomić rodzajem pytań na które
możecie natrafić w trakcie rozmowy kwalifikacyjnej związanej z językiem C#. W praktyce często okazuję się, że rozmowa zaczyna
się od postawowych zagadanień a następnie na podstawie Twoich odpowiedzi dyskusja jest kontynuowana - zagłębiamy się w szczegóły.
Czym jest C#?
C# jest nowoczesnym, w pełni obiektowym językiem programowania opracowanym przez firmę Microsoft. Programy napisane w języku C# są
kompilowane do języka pośredniego CIL (Common Intermediate Language), który jest wykonywany przez środowisko uruchomieniowe .NET Framework.
W systemie, w którym takie środowisko nie jest zainstalowane, nie ma możliwości wykonania tak skompilowanego programu.
Nazwa języka powstała w analogiczny sposób do języka C++. Operator używany w języku C, C++ czy C# jakim jest '++' oznacza zwiększenie o jeden.
Można zatem powiedzieć, że C++ to więcej niż C. W przypadku języka C# wykorzystano podobny pomysł. Symbol kratki przypomina połączone ze sobą
dwa operatory '++'.
Czym są tablice 'Jagged Array' w języku C#?
Jagged Array(tj. tablica postrzępiona) to tablica w tablicy.
Tablica taka może zostać zainicjowana w poniższy sposób:
Tablica scores zawiera w sobie dwie tablice liczb całkowitych. Pierwszy element scores[0] jest tablicą 3 liczb całkowitych,
element drugi scores[1] jest tablicą 4 liczb całkowitych.
Na ile sposobów można przekazać parametry do metody?
Istnieją trzy sposoby przekazywania parametrów do metody:
Parametry typu wartościowego(value parameters) - sposób ten pozwala na skopiowane aktualnej wartości argumentu i przekazanie jej w formie parametru do wnętrza metody. W tym przypadku zmiany parametru dokonane wewnątrz funkcji nie wpływają na argument. Oznacza to, iż do metody przekazaliśmy jedynie kopię tej wartości stąd zmiany dokonywane na niej nie mają wpływu na zmianę parametru, który przekazaliśmy do metody;
Parametry typu referencyjnego(reference parameters) - sposób ten kopiuje odniesienie do parametru w postaci adresu w pamięci do wnętrza funkcji. Oznacza to, iż zmiany wprowadzone wewnątrz tej funkcji będą miały wpływ na wartość argumentu;
Parametry typu wyjściowego(output parameters) - metoda ta pozwala na zwrócenie więcej niż jednej wartości.
Różnice pomiędzy ref i out : REF:
Przekazanie zmiennej przez adres a nie przez wartość (tak jak out);
Zmienna przekazana przez ref musi być wcześniej zainicjalizowana.
OUT
Przekazanie zmiennej przez adres a nie przez wartość (tak jak ref);
Zmienna przekazana przez out nie musi być wcześniej zainicjalizowana(ale może być).
Zmienna przekazana przez out musi być za to zainicjalizowana w ciele metody.
Czy można zwrócić wiele wartości z funkcji w C#?
Tak! W tym celu należy wykorzystać parametry wyjściowe(out). Instrukcja return pozwala na zwrócenie tylko jednej wartości podczas gdy użycie out pozwala na zwrócenie wielu parametrów.
Czym są przestrzenie nazw(namespace) w C#?
Przestrzenie nazw zostały zaprojektowane po to aby zachować odrębność jednych nazw od drugich. Nazwy klas zadeklarowane w jednej przestrzeni nazw nie są w konflikcie z takimi samymi nazwami klas zdeklarowanymi w innej przestrzeni nazw.
Jaki jest cel używania słowa kluczowego using w C#?
Using pozwala na dodanie przestrzeni nazw do naszego projektu. Na ogół program używa wielu takich instrukcji.
Z drugiej strony pozwala na tzw. "szybkie wykorzystanie obiektu". Po wyjściu z bloku using obiekt taki zostaje automatycznie usunięty.
Czym są typy wartościowe w C#?
Typy wartościowe to typy do których wartość może zostać przypisana bezpośrednio. Dziedziczą one z klasy System.ValueType.
Typy wartościowe zawierają dane bezpośrednio. Zaliczamy do nich m.in. int, char, float, double, long, byte, bool oraz typy wyliczeniowe jak enum, struct.
W momencie deklaracji zmiennych pamięć rezerwuje miejsce na przechowywanie tych danych.
Czym są typy referencyjne w C#?
Typy referencyjne nie przechowują rzeczywistych wartości a jedynie odniesienia(referencje) do zmiennych.
Do typu referencyjnych zaliczamy elementy rozszerzające klasę System.Object oraz System.String. Są nimi np. klasy, delegaty, interfejsy, tablice, zmienne string. Typów takich nie da się kopiować korzystając z operatora przypisania.
Głównym różnicą pomiędzy tymi typami jest to, że w przypadku typów referencyjnych nigdy nie wiemy ile miejsca będzie zajmować klasa. W przypadku typów wartościowych ta wartość jest znana.
Tworząc typy referencyjny kompilator wrzuca na stos referencje, która wskazuje na konkretną lokalizację w pamięci. Obiekt taki może się zmieniać w trakcie działania programu przez co może się również zmieniać jego rozmiar w pamięci. Nad takimi operacjami czuwa Garbage Collector który działy w osobnym wątku naszej aplikacji.
Czym jest boxing w C#?
Boxing jest operacją w której tym wartościowy jest konwertowany na typ obiekt(object).
int i = 123;
// Poniższa linijka dokonuje operacji boxing object o = i;
Czym jest unboxing w C#?
Unboxing jest operacją w której typ obiekt(object) jest konwertowany na typ wartościowy.
o = 123;
i = (int)o; // unboxing
Czym są typy dynamiczne w C#?
W typach dynamicznych można przechowywać każdy rodzaj wartości. Typ przechowywanej wartości będzie dopiero sprawdzany w momencie wykonywania aplikacji.
Składnia typów dynamicznych jest następująca:
dynamic nazwa_zmiennej = value;
Jaka jest różnica pomiędzy typami dynamicznymi a wartościowymi?
Typy dynamiczne są podobne do typów wartościowych z tą różnicą, że sprawdzenie typów wartościowych odbywa się w trakcie kompilacji kodu a sprawdzenie typów dynamicznych odbywa się w trakcie wykonywania aplikacji.
Czym są wskaźniki(pointer) w C#?
Na wstępie warto powiedzieć, że język C# obsługuje wskaźniki ale w ograniczonym zakresie. Wskaźnik jest niczym innym niż zmienna, która przechowuje adres pamięci dla innego typu. Jednakże w języku C# wskaźnik może trzymać adres jedynie dla typów wartościowych i tablic.
Przykład użycia:
int* ptr;
Deklarujemy wskaźnik zmiennej ptr która składuje adres dla zmiennej typu int. Operator odniesienia(reference operator) – ‘&’ może być użyty do pobrania adresu w pamięci.
int x = 100;
Wyrażenie &x zwraca nam adres pamięci zmiennej x, którą możemy przypisać do zmiennej wskaźnikowej.
int *ptr = &x;
Podsumowanie:
Console.WriteLine((int)ptr); // Wyświetla adres pamięci
Console.WriteLine(*ptr); // Wyświetla wartość przechowywaną w pamięci
Należy również zaznaczyć, że używanie wskaźników w języku C# wymaga użycia tzn. unsafe context. Polecenia takie wykonywane są poza kontrolą Garbage Collector.
Co należy wiedzieć o Garbage Collector?
W C# Garbage Collector wykonuje mnóstwo czynności za nas. Zajmuje się automatycznym czyszczeniem pamięci przez co można odnieść wrażenie, że w wielu firmach zamiast dążenia do optymalizacji kodu dokłada się po prostu pamięć RAM do serwerów i tyle. Ma to oczywiście swoje plusy, taka aplikacja jest prostsza i czytelniejsza ale za cenę ignorancji w sprawy związane z pamięcią.
GC(Garbage Collector) traktuje wszystkie obiekty jako niepotrzebne o ile nie zostało zaznaczone inaczej. Zakłada, że wszystkie obiekty na stercie są krótkotrwałe. Oznacza to, iż szybciej zostanie usunięty obiekt, który krócej przebywa na stercie niż ten który znajduje się tam dłużej.
Czym jest zatem sterta?
Sterta jest częścią pamięci wirtualnej jaka jest przydzielana aplikacji podczas uruchamiania. Sterta jest oddzielna dla każdej uruchamianej aplikacji.
Wszystkie obiekty, które trafiają do pamięci są umieszczane w 2 miejscach na stosie i/lub stercie. Stos z kolei jest o wiele szybszy niż sterta, lądują na niego zmienne oraz parametry przekazywane do funkcji. Jest on czyszczony, gdy kończy się dana metoda. O to dba CLR (Common Language Runtime), czyli środowisko uruchomieniowe dla platformy .NET. Środowisko CLR kompiluje i wykonuje kod zapisany w języku pośrednim (CIL).
Gdyby w .NET nie było GC każdy z nas musiałby sam zajmować się nieużywanymi obiektami i zarządzaniem pamięcią – tak jak ma to miejsce np. w języku C++.
Za każdym wywołaniem GC sprawdza on, które obiekty na stosie mają powiązania z polem klasy, zmienną w metodzie czy globalną zmienną. Po sprawdzeniu stosu, elementy, które mają jakieś referencję są zbierane razem a reszta obiektów jest usuwana z pamięci.
Garbage Collector uruchamia się przynajmniej w 2 momentach. W momencie gdy kończy się pamięć na stercie lub gdy OS zwraca informację o braku pamięci.
Jaki jest cel używania operatora is w C#?
Operator is jest operatorem rzutowania. Zwraca flagę true jeżeli rzutowanie się powiedzie. Zwraca flagę false jeżeli rzutowanie będzie niepoprawne. Operator ten zwykle znajduje zastosowanie podczas sprawdzania typów w instrukcji warunkowej if
Przykład użycia:
int i = 3;
if(i is object) // Sprawdzenie czy 'i' jest objektem(object)- zwraca true if(i is string) // Sprawdzenie czy 'i' jest zmienną typu string - zwraca false
Jaki jest cel używania operatora as w C#?
Operator as dokonuje rzutowania bez zwracania wyjątku jeżeli rzutowanie się nie powiedzie.
Przykład użycia:
int i = 3;
string result = i as string;// Rzutowanie nieudane - result przyjmuje wartość null (nie zwraca wyjątku)
Czym jest enkapsulacja?
Enkapsulacja, inaczej hermetyzacja to termin oznaczający ukrywanie w obiektach tego do czego użytkownik nie powinien mieć dostępu. Idea nowoczesnego programowania obiektowego to idea pisania kodu jak najbardziej zamkniętego. Dzięki takiemu podejściu ograniczamy dostęp do części zmiennych, metod, które są wykonywane wewnątrz konkretnej klasy. Dostęp do tych elementów jest ograniczony "z zewnątrz".
Aby wykorzystać hermetyzację należy zapoznać się z pojęciem modyfikatorów dostępu. Określają one dostępność do wskazanego elementu w naszym kodzie źródłowym.
Modyfikatory dostępu w C#:
Public – brak ograniczeń. Składowe oznaczone tym słowem kluczowym są dostępne dla wszystkich metod wszystkich klas;
Private – składowe te dostępne są tylko dla metod klas w której się znajdują;
Protected – składowe chronione są dostępne dla klasy, w której się znajdują oraz dla klas dziedziczących w danej bibliotece;
Internal – składowe wewnętrze są dostępne dla klasy znajdującej się w danej bibliotece;
Protected Internal – w języku C# nie ma modyfikatora dostępu, który łączyłby kombinacje słów kluczowych protected lub internal. Klasa, która dziedziczy po tej klasie ma dostęp do tych pól nawet jeżeli znajduje się w innej bibliotece.
Czym są typy dopuszczające wartości puste(nullable types) C#?
Język C# dostarcza specjalne typy, tzw. typy dopuszczające wartości puste, do których można przypisać nominalny zakres wartości oraz wartości typu null.
Dla przykładu, możemy zapisać dowolną wartość z zakresu -2,147,483,648 – 2,147,483,647 lub wartość null w zmiennej: Nullable<Int32>. Podobnie do zmiennej Nullable<bool> możemy przypisać flagę true, false lub wartość null.
Jak używać operatorów ? oraz ?? w języku C#?
Operatory te stosuje się aby skrócić zapis często używanej konstrukcji if (..) then .. else .. .
Przykłady użycia: condition ? expression1 : expression2
Jeżeli warunek jest prawdziwy zostanie zwrócona wartość expression1, w przeciwnym wypadeku wartość expression2.
Użycie kolejnego operatora(??) jeszcze bardziej skróci ten zapis. Operator ten zwróci wartość po swojej lewej stronie wtedy, gdy nie jest ona równa null. W przeciwnym wypadku zwróci wartość po stronie prawej.
Poniżej przykład zastosowania:
int CompareNumbers(int a, int b)
{
int? wynik = null;
return wynik ?? -1;
}
W przykładzie tym zostanie zwrócona liczba -1 ponieważ wartość po lewej stronie jest null'em.
Czy w języku C# można utworzyć metodę, która może przyjmować zmienną liczbę parametrów?
Tak, należy użyć słowa kluczowego params. Metoda taka może przyjąć zmienną liczbę parametrów. Liczba parametrów może być również zerowa.
Przykład użycia:
int SumParameters(params int[] values)
{
int sum = 0;
foreach (var item in values)
{
sum += item;
}
return sum;
}
Oraz przykładowe wywołania metody: int params1 = SumParameters(1, 2, 3, 4); int params2 = SumParameters(144, 21);
Używając słowa kluczowego params możesz przekazać dodatkowe parametry?
Można, ale należy zwrócić uwagę, iż te parametry muszą wystąpić przed użyciem słowa kluczowego params.
Przykład poprawnego użycia:
int SumParameters(string example, params int[] values)
{
int sum = 0;
foreach (var item in values)
{
sum += item;
}
return sum;
}
Oraz przykładowe wywołania metody: int params1 = SumParameters("DodatkowyParametr" 1, 2, 3, 4); int params2 = SumParameters("DodatkowyParametr", 144, 21);
Która klasa jest klasą bazową dla wszystkich tablic(arrays) w języku C#?
Klasa Array jest klasą bazową dla wszystkich tablic w języku C#. Jest ona zdefiniowana w przestrzeni nazw System.
Klasa Array dostarcza różne właściwości oraz metody do pracy z tablicami.
Sortowanie tablic w języku C#.
Należy wykorzystać metodę Array.sort(array). Funkcja ta sortuję elementy jednowymiarowej tablicy używając implementacji interfejsu ‘IComparable’ dla każdego elementu tablicy.
Elementy takie zostaną posortowanie w kolejności rosnącej.
Jeżeli chcemy posortować elementy w kolejności malejącej należy użyć metody Array.reverse(array).
Interfejs IComparable umożliwia sortowanie obiektów niestandardowych gdy zostanie zaimplementowany. Kiedy klasa implementuje ten interfejs należy dodać metodę publiczną CompateTo(T).
Możemy zaimplementować swoje własne sortowanie dla klasy, która implementuje ten interfejs.
Czym są struktury w C#?
Struktura w języku C# to wartościowy typ danych(value type). Struktura pozwala na utworzenie pojedynczej zmiennej, która może przechowywać dane różnych typów. Do utworzenia struktury używamy słowa kluczowego struct.
Struktury używamy do reprezentacji rekordu.
Jakie są różnice pomiędzy klasą a strukturą w C#?
Główne różnice pomiędzy klasą a strukturą to:
klasy są typem referencyjnym a struktury są typem wartościowym;
struktury nie mogą dziedziczyć po sobie;
struktury nie mogą mieć domyślnego konstruktora.
Czym jest typ wyliczeniowy(enum) w języku C#?
Typ wyliczeniowy to zbiór nazwanych, stałych liczb całkowitych. Typ wyliczeniowy jest deklarowany za pomocą słowa kluczowego enum.
Jest to wartościowy typ danych. Innymi słowy, jest to typ, który ma własne wartości i nie może on dziedziczyć po innych typach wyliczeniowych.
Typy wyliczeniowe są często używany do opisu obecnego stanu aplikacji:
W powyższym przykładzie wartości zostały dodane przez użytkownika. W momencie jeżeli statusy te nie zostaną zdefiniowane
zostaną one dodane automatycznie. Pierwszy element w strukturze będzie posiadał wartość 0.
W wypadku używania struktur, możemy posługiwać się zarówno zbiorem nazw jak i zdefiniowanym zbiorem liczb całkowitych.
Dobrym przykładem może być logowanie do systemu oraz nadawanie uprawnień użytkownikowi. Posiadając zdefiniowany typ wyliczeniowy oraz
nadając coraz wyższe numery wyższym prawą dostępu możemy robić proste sprawdzenia za pomocą instrukcji warunkowej if, kto ma dostęp
do jakiego modułu.
Jaki jest domyślny modyfikator dostępu dla klasy?
Domyślny modyfikator dostępu dla klasy to internal - składowe wewnętrze są dostępne dla klasy znajdującej się w danej bibliotece.
Jaki jest domyślny modyfikator dostępu dla składników klasy?
Składniki klasy w C# to elementy, które reprezentują dane oraz zachowanie klasy.
Domyślny modyfikator dostępu to private - składowe te dostępne są tylko dla metod klas w której się znajdują.
Czym jest dziedziczenie w C#?
Dziedziczenie jest kluczowym mechanizmem obiektowości. Pozwala na powielenie funkcjonalność wobec różnych klas dzięki czemu nie musimy ciągle powielać tego samego kodu.
Prostym i często przedstawianym przykładem jest przykład Ssaków, jako dużej kategorii zwierząt, w której też zawiera się człowiek. Wszystkie ssaki mają swoje wspólne cechy, jak np. faza snu czy sposób karmienia. Jednakże każda z tych podgrup ma swoje specjalne cechy.
Kolejny często spotykany przykład to klasa Figury. Prostokąt, trójkąt, mają wspólne cechy, są figurami. Każda z nich ma jednak swoje cechy specjalne. Problem polega na tym, aby ustalić ich cechy wspólne, które zostaną wyodrębnione w postaci klasy bazowej. Nie chcemy dla każdej z tych powtarzać wspólnych cech. Jest to najkrótsze podsumowanie modelowania klas z uwzględnieniem dziedziczenia. Rozbudowa takiej aplikacji jest zdecydowanie prostsza gdyż dodają klasę dla kolejnej figury nie musimy kopiować/powielać wspólnych metod.
Klasa, która dziedziczy posiada wszystkie pola oraz metody klasy bazowej. Pojawia się pytanie, jak te pola są inicjowane i tak działa konstruktor w wypadku dziedziczenia? Wywołanie konstruktora klasy bazowej odbywa się za pomocą słowa kluczowego base, a w nim trzeba podać odpowiednie argumenty. Wywołanie takie będzie możliwe pod warunkiem, że konstruktor w klasie bazowej nie będzie prywatny.
Czy wielokrotne dziedzicznie klas jest możliwe w C#?
Nie! C# nie obsługuje wielokrotnego dziedziczenia klas.
Czym jest polimorfizm w C#?
Polimorfizm to jeden z czterech podstawowych założeń dotyczących programowania obiektowego. Mówiąc w największym uproszczeniu to zdolność obiektu do różnych zachowań zależnie od bieżącego wykonania programu.
Metody, które obsługują mechanizm polimorfizmu w C# to metody wirtualne oraz abstrakcyjne.
Warto nadmienić, że polimorfizm dzieli się na:
statyczny (przeciążanie funkcji i operatorów)
dynamiczny (funkcje wirtualne i funkcje abstrakcyjne)
Zacznijmy od polimorfizmu statycznego. W danej klasie może istnieć wiele metod o tej samej nazwie, które różnią się tylko liczbą parametrów. Przeciążenie takie jest także nazywane polimorfizmem.
Polimorfizm ten zachodzi podczas kompilacji programu. Nie mamy wpływu na zachowanie takich metod w trakcie wykonywania programu. Metoda, która zostanie użyta została wybrana na etapie kompilacji a jej wybór zależy od ilości parametrów przekazanych do metody.
Wyjaśnimy teraz polimorfizm dynamiczny, tj. przesłanianie funkcji. Aby skorzystać z polimorfizmu dynamicznego musimy utworzyć w danej klasie metodę polimorficzną. Będzie to metoda wirtualna albo metoda abstrakcyjna. Metody polimorficzne przesłania się słowem kluczowym override.
Należy pamiętać, że metoda wirtualna może być przesłonięta w klasie pochodnej – ale nie musi. Jeżeli nie będzie przesłonięta zostanie wywołana metoda klasy bazowej.
W przypadku użycia metody abstrakcyjnej w klasie pochodnej musi ona zostać przesłonięta.
Czym jest przeciążanie metod w C#?
W danej klasie może znajdować się wiele metod o tej samej nazwie. Ich deklaracja musi się jednak różnić od siebie
inną deklaracją typów i/lub ilością argumentów. Warto pamiętać, że nie można przeciążyć deklaracji metody, która różni się jedynie przez zwracany typ.
Modyfikator dostępu Sealed w C#.
Klasa opatrzona tym modyfikator dostępu mówi, iż klasa ta nie może być dziedziczona.
Użycie modyfikatora może być związane z wydajnością. Należy pamietać, że wywoływanie metod wirtualnych jest trochę wolniejsze,
gdyż należy sprawdzić, która dokładnie metoda ma zostać wywołana(polimorfizm). Warto również wspomnieć, iż modyfikator sealed może zostać zastosowany również do metod przeciążonych.
Jak utworzyć klasę abstrakcyjną opatrzoną modyfikatorem Sealed w C#?
W języku C# nie ma takiej możliwości. Nie można utworzyć klasy abstrakcyjnej opatrzonej modyfikatorem sealed.
Czym są metody wirtualne w języku C#?
Metody wirtualne pozwalają nam na zastosowanie mechanizmu polimorfizmu dynamicznego. W klasie bazowej przygotowujemy metodę
wirtualną(może być zaimplementowana lub nie). Następnie w klasach dziedziczących po klasie bazowej możemy
przeciążyć tą metodę wymuszając implementację specyficzną dla danej klasy. Jeżeli metoda ta w klasie dziedziczącej
nie zostanie przeciążona zostanie użyta domyślna implementacja z klasy bazowej.
Czym jest interfejs w C#?
Interfejs jest popularnym mechanizmem, który gwarantuje, że dana klasa lub struktura, który po nim dziedziczy obsługuje dane zachowania.
Klasa, która wykorzystuje dany interfejs musi udostępniać wszystkie elementy tego interfejsu. Interfejs nie zawiera żadnego kodu tylko specyfikacje metod i jej właściwości.
Ogólne zasady używania interfejsów:
nie można dodawać pól, nawet statycznych;
nie można definiować konstruktora;
nie można dodawać modyfikatorów dostępu, wszystkie są publiczne
nie można zagnieżdżać klas, struktur, typów wyliczeniowych i innych interfejsów wewnątrz interfejsu;
nie można dziedziczyć po klasie. Interfejs może jednak dziedziczyć zachowanie po innym interfejsie. Dziedziczenie po klasie jest niemożliwe ponieważ zawiera ona implementacje kodu a to łamie główną zasadę interfejsów.
Czym są dyrektywy preprocesora w C#?
Dyrektywy te pozwalają na przekazanie instrukcji do kompilatora przez rozpoczęciem rzeczywistego procesu kompilacji.
Wszystkie dyrektywy rozpoczynają się od znaku #. Nie są ona traktowane jak deklaracje stąd nie muszą się kończyć znakiem średnika(;). Traktowane są jako pomoc przy warunkowej kompilacji.
Przykładowe i popularne dyrektywy:
#if, sposób użycia: $IF DEBUG … - możemy np. wyświetlać informacje w konsoli podczas sprawdzania naszego kodu;
#region oraz #endregion – pozwala na ukrywanie bloku kodu wraz z nadaniem mu nazwy. Czytelniejszy kod w przypadku wielu linii kodu, np. grupowanie metod;
#error – pozwala na wygenerowanie błędu w określonej przez programistę lokalizacji kodu;
#define, #undefine – tworzenie zmiennej typu boolean, która może być wykorzystywana w warunkowym sprawdzaniu kodu. #define nazwa_zmiennej
określa flagę naszej zmiennej na true, w przypadku użycia dyrektywy #undef nazwa_zmiennej flaga będzie ustawiona na wartość false
Która klasa jest klasą bazową dla wszystkich wyjątków w C#?
Wyjątki w języku C# są reprezentowane przez klasy. Klasy te przeważnie dziedziczą pośrednio lub bezpośrednio z
klasy System.Exceptions. Niektóre z klas, które dziedziczą po System.Exceptions to System.ApplicationException oraz System.SystemException.
Jaka jest różnica pomiędzy klasą System.ApplicationException oraz klasą System.SystemException?
Klasa System.ApplicationException obsługuje wyjątki wygenerowane przez aplikację. Stąd, wszystkie wyjątki zdefiniowane przez programistę będą pochodziły z tej klasy.
W przypadku klasy System.SytemException jest to klasa bazowa dla wszystkich wcześniej zdefiniowanych wyjątków systemowych.