Pytania przygotowane tak, żebyś mógł zaznajomić się z tym czego możesz się spodziewać podczas rozmowy kwalifikacyjnej z C#. Kliknij pytanie żeby zobaczyć odpowiedź, a gdy je znasz — oznacz jako zaliczone ✓
C# jest nowoczesnym, w pełni obiektowym językiem programowania opracowanym przez firmę Microsoft. Programy napisane w C# są kompilowane do języka pośredniego CIL (Common Intermediate Language), który jest wykonywany przez środowisko uruchomieniowe .NET Framework.
Nazwa języka powstała analogicznie do C++. Symbol kratki # przypomina połączone dwa operatory ++, co symbolizuje "więcej niż C++".
Jagged Array (tablica postrzępiona) to tablica w tablicy. Każda podtablica może mieć inny rozmiar.
Tablica scores[0] zawiera 3 liczby całkowite, a scores[1] — 4 liczby całkowite.
Istnieją trzy sposoby przekazywania parametrów do metody:
Tak! W tym celu należy użyć parametrów wyjściowych out. Instrukcja return pozwala zwrócić tylko jedną wartość, natomiast użycie out umożliwia zwrócenie wielu parametrów. W nowszych wersjach C# można też używać Tuple lub dekonstrukcji.
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 zadeklarowanymi w innej przestrzeni nazw.
using pozwala na dodanie przestrzeni nazw do 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 — implementuje on wzorzec IDisposable.
Typy wartościowe to typy, do których wartość może zostać przypisana bezpośrednio. Dziedziczą z klasy System.ValueType. Zawierają dane bezpośrednio w pamięci stosu (stack).
Zaliczamy do nich: int, char, float, double, long, byte, bool oraz typy wyliczeniowe (enum) i struktury (struct).
Typy referencyjne nie przechowują rzeczywistych wartości, a jedynie odniesienia (referencje) do danych na stercie (heap). Do typów referencyjnych zaliczamy klasy, delegaty, interfejsy, tablice i string.
Główna różnica: w przypadku typów referencyjnych nie wiadomo z góry ile miejsca zajmie obiekt. Kompilator umieszcza referencję na stosie, a sam obiekt na stercie — zarządzanej przez Garbage Collector.
Boxing to operacja, w której typ wartościowy jest konwertowany na typ obiektowy (object). Wartość jest "pakowana" i umieszczana na stercie.
Unboxing to odwrotna operacja względem boxing — typ obiektowy (object) jest konwertowany z powrotem na typ wartościowy. Wymaga jawnego rzutowania i może rzucić InvalidCastException.
W typach dynamicznych można przechowywać każdy rodzaj wartości. Typ przechowywanej wartości będzie dopiero sprawdzany w momencie wykonywania aplikacji (runtime), a nie w czasie kompilacji.
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 (runtime).
C# obsługuje wskaźniki w ograniczonym zakresie. Wskaźnik to zmienna przechowująca adres pamięci innego typu — wyłącznie typów wartościowych i tablic.
Używanie wskaźników wymaga tzw. unsafe context. Operacje takie wykonywane są poza kontrolą Garbage Collector.
W C# Garbage Collector (GC) zajmuje się automatycznym czyszczeniem pamięci. GC traktuje wszystkie obiekty jako niepotrzebne, o ile nie zaznaczono inaczej — zakłada, że szybciej zostanie usunięty obiekt krótkotrwały niż długotrwały.
Sterta (heap) to część pamięci wirtualnej przydzielana aplikacji, oddzielna dla każdej uruchamianej instancji. Obiekty trafiają na stertę, a GC — działający w osobnym wątku — zwalnia te, do których nie ma już referencji.
GC uruchamia się m.in. gdy kończy się pamięć na stercie lub gdy OS sygnalizuje jej brak. Gdyby w .NET nie było GC, każdy programista musiałby sam zarządzać pamięcią — jak w C++.
Operator is jest operatorem rzutowania. Zwraca true jeżeli rzutowanie się powiedzie, false jeżeli będzie niepoprawne. Stosowany najczęściej w instrukcji warunkowej if.
Operator as dokonuje rzutowania bez rzucania wyjątku, gdy rzutowanie się nie powiedzie — zamiast tego zwraca null.
Enkapsulacja (hermetyzacja) to ukrywanie elementów, do których użytkownik nie powinien mieć dostępu. Ograniczamy dostęp do zmiennych i metod wykonywanych wewnątrz klasy przez modyfikatory dostępu.
Nullable types pozwalają przypisać typowi wartościowemu zarówno jego normalny zakres wartości, jak i wartość null.
Dla przykładu, do zmiennej Nullable<Int32> można przypisać dowolną wartość z zakresu int lub null. Skrócona składnia to int?.
Operatory te skracają zapis konstrukcji if..else:
Tak, używając słowa kluczowego params. Metoda taka może przyjąć zmienną liczbę parametrów, w tym zero.
Można, ale dodatkowe parametry muszą wystąpić przed słowem kluczowym params.
Klasa Array jest klasą bazową dla wszystkich tablic w C#, zdefiniowaną w przestrzeni nazw System. Dostarcza właściwości i metody do pracy z tablicami, takie jak Length, Sort(), Reverse() czy IndexOf().
Należy użyć metody Array.Sort(array) — sortuje elementy tablicy rosnąco używając implementacji IComparable. Dla kolejności malejącej stosujemy Array.Reverse(array).
Interfejs IComparable umożliwia sortowanie obiektów niestandardowych — wystarczy zaimplementować metodę CompareTo(T).
Struktura w C# to wartościowy typ danych (value type). Pozwala na utworzenie pojedynczej zmiennej przechowującej dane różnych typów — deklarowana słowem kluczowym struct. Używana do reprezentacji lekkich rekordów danych.
Typ wyliczeniowy to zbiór nazwanych, stałych liczb całkowitych. Deklarowany słowem kluczowym enum. Jest wartościowym typem danych — nie może dziedziczyć po innych typach wyliczeniowych.
Jeśli wartości nie zostaną zdefiniowane, zostaną przypisane automatycznie — pierwszy element dostaje wartość 0.
Domyślny modyfikator dostępu dla klasy to internal — składowe wewnętrzne są dostępne tylko dla klas w tej samej bibliotece (assembly).
Składniki klasy (pola, metody, właściwości) mają domyślny modyfikator private — dostępne tylko dla metod tej samej klasy.
Dziedziczenie to kluczowy mechanizm obiektowości — pozwala klasom pochodnym przejąć pola i metody klasy bazowej bez ich powielania. Klasa dziedzicząca ma dostęp do publicznych i chronionych składowych klasy bazowej.
Wywołanie konstruktora klasy bazowej odbywa się za pomocą słowa kluczowego base — pod warunkiem, że konstruktor bazowy nie jest prywatny.
Nie — C# nie obsługuje wielokrotnego dziedziczenia klas. Klasa może dziedziczyć tylko po jednej klasie bazowej. Wielokrotne dziedziczenie można zastąpić implementacją wielu interfejsów.
Polimorfizm to zdolność obiektu do różnych zachowań zależnie od bieżącego wykonania. Wyróżniamy dwa rodzaje:
W danej klasie może znajdować się wiele metod o tej samej nazwie, o ile różnią się liczbą lub typami parametrów. Nie można przeciążyć metody, która różni się jedynie typem zwracanej wartości.
Klasa opatrzona modyfikatorem sealed nie może być dziedziczona. Użycie tego modyfikatora może przynieść zysk wydajnościowy — wywołanie metod wirtualnych jest nieco wolniejsze, bo wymaga sprawdzenia polimorfizmu. Modyfikator sealed można stosować również do przesłoniętych metod.
W języku C# nie ma takiej możliwości. Nie można jednocześnie użyć modyfikatorów abstract i sealed dla tej samej klasy — są to wzajemnie wykluczające się pojęcia.
Metody virtual pozwalają na zastosowanie polimorfizmu dynamicznego. W klasie bazowej przygotowujemy metodę wirtualną (z opcjonalną implementacją). Klasy dziedziczące mogą ją przesłonić słowem kluczowym override.
Jeśli klasa dziedzicząca nie przesłoni metody, zostanie użyta domyślna implementacja z klasy bazowej.
Interfejs definiuje kontrakt — zestaw metod i właściwości, które klasa musi implementować. Nie zawiera kodu implementacji (z wyjątkiem default interface methods w C# 8+).
Dyrektywy preprocesora przekazują instrukcje do kompilatora przed rzeczywistą kompilacją. Zaczynają się od znaku #, nie kończą się średnikiem. Popularne dyrektywy:
Wyjątki w C# są reprezentowane przez klasy, które dziedziczą pośrednio lub bezpośrednio z klasy System.Exception. Dwie ważne podklasy to System.ApplicationException oraz System.SystemException.
System.ApplicationException obsługuje wyjątki wygenerowane przez aplikację. Wszystkie wyjątki zdefiniowane przez programistę powinny pochodzić z tej klasy.
System.SystemException to klasa bazowa dla wszystkich wcześniej zdefiniowanych wyjątków systemowych (.NET runtime).