Typy proste są przechowywane na stosie (stack) i bazują na wartości. Przypisując jedną zmienną do drugiej - zostanie przepisana jej wartość, np. 5. Typy referencyjne są przechowywane na stercie (heap). Nie przechowują wartości, ale wskazują one na obszar w pamięci. Jeśli przypiszemy taki obiekt do innego, to zmieniając wartość dowolnego - zmieni się w obu.
Oba przekazują zmienną przez referencję/adres w pamięci, zamiast przez wartość. Zmienna przekazana przez ref musi być wcześniej zainicjalizowana.
Klasa to definicja teoretyczna, a obiekt to instancja danej klasy.
Interfejs - potocznie nazywany kontraktem - to opis zachowania (właściwości, metody, zdarzenia), które implementująca go klasa musi spełnić. Nie można utworzyć instancji interfejsu, a jego metody są domyślnie publiczne.
Domyślnie wszystko w interfejsie jest public. Od wersji języka C# 8 została dodana obsługa private i protected.
Klasa abstrakcyjna zawiera abstrakcyjne metody/właściwości, które będą musiały zostać zaimplementowane w klasie dziedziczącej. Może posiadać zaimplementowane metody oraz implementować interfejsy. Nie można utworzyć instancji klasy abstrakcyjnej. Często mówi się na nią “klasa bazowa” (base class).
To sygnatura metody, którą będzie musiała zaimplementować klasa dziedzicząca.
Klasa abstrakcyjna może posiadać zaimplementowane metody.
Tak.
Tak.
Interfejs jest kontraktem, który dana klasa musi zaimplementować. Nie da się utworzyć instancji interfejsu.
Jest to metoda, którą można nadpisać lub rozszerzyć w klasie dziedziczącej. Klasa dziedzicząca używa słowa kluczowego override i możemy zostawić bazowe wywołanie, lub je przysłonić.
Boxing to zmiana typu wartościowego na object, a unboxing to w drugą stronę. Unboxing jest jawny, bo musimy podać typ, na który rzutujemy, np. (int).
Const można stosować tylko do typów prostych (wartościowych) i jest skompilowany do biblioteki. Readonly jest ustawiany dopiero przy starcie aplikacji i może być stosowany zarówno do typów prostych jak i referencyjnych. Jak wiemy że coś jest stałe to lepiej zrobić const, np const float PI = 3.14
W uproszczeniu to do obu można przypisać dowolne wartości, ale poprawność objecta jest sprawdzana w czasie kompilacji, a dynamic dopiero w czasie wykonywania programu. Przydatne do integracji z innymi językami / COM interop.
As przypisze zmienną do typu który podamy, ale jak nie uda mu się to otrzymamy null zamiast wyjątku. Is zwraca bool, więc można go użyć w instrukcji warunkowej żeby sprawdzić czy coś jest danym typem.
Możemy ukryć implementację danej biblioteki / fragmentu kodu, bez udostępniania szczegółów implementacyjnych. W C# zaimplementowano ją poprzez modyfikatory dostępu.
Niczym. Enkapsulacja to angielskie słowo, a hermetyzacja to jego polski odpowiednik.
Powoduje, że po klasie nie można dziedziczyć.
- public - zmienna widoczna jest na zewnątrz klasy
- private - zmienna jest widoczna tylko wewnątrz klasy, ukryta na zewnątrz
- protected - zmienne chronione są widoczne też w klasie która podziedziczyła
- internal - jak public, ale tylko w obrębie assembly (biblioteki)
- protected internal - to kombinacja tych obu
You Ain’t Gonna Need It. Nie pisz kodu dla potencjalnych przyszłych funkcjonalności. Zrób prostą implementację, a jeśli będzie potrzeba, to się ją zmieni.
Polimorfizm jest zdolnością do różnych zachowań, w zależności od kontekstu. Przykład: obiekty różnego typu mogą być sprowadzone do wspólnego mianownika, np. poprzez implementację interfejsu. Następnie metoda może przyjąć ten interfejs w parametrze, co prowadzi do skrócenia kodu programu. Np. wszystkie komendy i query w bibliotece MediatR implementują IRequest.
Klasa StringBuilder jest bardziej wydajna, niż ręczne łączenie stringów znakiem ‘+’, chociaż w najnowszych wersjach .NET różnica jest naprawdę bardzo minimalna.
1. Enkapsulacja – dzięki modyfikatorom dostępu możemy ukryć detale implementacyjne obiektu, a na zewnątrz udostępnić tylko pożądane przez nas zachowania.
2. Dziedziczenie – dzięki któremu części wspólne zachowań obiektów mogą być zdefiniowane w jednym miejscu.
3. Polimorfizm – który pozwala na inne zachowanie danego obiektu, w zależności od tego kto o niego odpytuje.
Czasami pytanie jest o 4 filary – wtedy dodatkowo opisuje się abstrakcję.
Command Query Separation to oddzielenie funkcjolaności na poziomie klasy. Metoda albo coś zwraca - i możemy ją bezpiecznie wywołać 100 razy, wiedząc, że nie zmieni stanu systemu - albo coś zmienia i zwraca void. CQRS jest przeniesieniem tej idei na poziom infrastruktury.
-
Przy implementacji fabryki / singletona.
-
Umożliwiają zrobienie klasy bazowej, z której mogą dziedziczyć tylko subklasy:
public abstract class BaseClass { private BaseClass() { } public class SubClass1 : BaseClass { public SubClass1() : base() { } } public class SubClass2 : BaseClass { public SubClass2() : base() { } } } -
Ułatwiają tworzenie bazowego konstruktora, wywoływanego przez inne, np.
public class MyClass { private MyClass(object data1, string data2) { } public MyClass(object data1) : this(data1, null) { } public MyClass(string data2) : this(null, data2) { } public MyClass() : this(null, null) { } }
S – Single Responsibility Principle – klasa powinna mieć tylko jeden powód do zmiany. Promuje to tworzenie małych, spójnych klas.
O – Open-Closed Principle – klasa powinna być otwarta na rozszerzenia, ale zamknięta na modyfikacje. Promuje ona korzystanie z polimorfizmu przez interfejsy i klasy bazowe, aby dodawać nowe funkcje do systemu.
L – Liskov Substitution Principle – gdy zastąpimy klasę podziedziczoną jej rodzicem to program powinien dalej poprawnie działać. Jest to pilnowanie, czy poprawnie realizujemy polimorfizm.
I – Interface Segregation Principle – klasy nie mogą zależeć od interefejsów, których nie potrzebują. Promuje ona robienie małych i spójnych interfejsów.
D – Dependency Inversion Principle – moduły wysokiego poziomu nie powinny być zależne od modułów niskiego poziomu. Oba typy powinny polegać na abstrakcji. Prowadzi to do kodu, w którym łatwiej wprowadza się zmiany, i który łatwiej testować.
Do przekazania dowolnej ilości parametrów poprzez jednowymiarową tablicę do metody. Np params int[] albo params string[]. Jeśli użyjemy params to nie możemy przekazać drugiego argumentu do metody.
-
Wstrzykiwanie zależności (Dependency Injection).
-
Wzorzec Obserwator / publikowanie zdarzeń (event).
Lazy loading polega na odsunięciu w czasie załadowania danych, do momentu w którym będą potrzebne. Jest to domyślne zachowanie Entity Framework. Eager loading polega od razu na załadowaniu większej ilości danych (przez Include w EF). Jest to przydatne, gdy chcemy przeprowadzić dużo operacji na danym zestawie danych i oszczędzić czas, który zejdzie na komunikację z serwerem.
-
Nie może być bezparametrowy.
-
Musi przypisywać wartości do każdej property w struct.
Tak
Używając metod Array.Sort() a potem Array.Reverse().
Używając metody Distinct() z namespace System.Linq
IEnumerator reprezentuje iterator. Używamy metody MoveNext() żeby szedł dalej, metodą Current() sprawdzamy aktualny stan iteracji. IEnumerable dziedziczy po IEnumerator. Reprezentuje on kolekcję. Możemy wywołać na nim foreach.
IQueryable dziedziczy po IEnumerable. IQueryable zastosuje warunek z Where() dopiero przy odpytywaniu SQLa, a IEnumerable najpierw pobierze wszystkie dane i przefiltruje je w pamięci aplikacji.
Yield oszczędza nam trochę pamięci, bo nie musimy tworzyć tymczasowych list w metodach. Znam dwa zastosowania yield:
-
Custom iteration: filtrowanie list.
-
Stateful iteration: wychodzenie z foreacha i powrót do niego po następny element, gdy metoda przechowuje swój prywatny stan.
Dzięki delegatom możemy przekazywać funkcje jako parametry metody.
- Action - oczekuje funkcji, która może przyjąć do kilkunastu parametrów, nic nie zwraca
- Func - oczekuje funkcji, może przyjąć do kilkunastu parametrów, ostatni parametr jest zwracany.
- Predicate - oczekuje funkcji, która przyjmuje jeden parametr i zwraca bool, To samo można zrobić używając Func<?, bool>.