Analiza systemu typów platformy .NET

Ukończone

C# to silnie typizowany język. Każda zmienna i stała mają typ, podobnie jak każde wyrażenie, które oblicza wartość. Biblioteka klas platformy .NET definiuje wbudowane typy liczbowe i złożone, które reprezentują szeroką gamę konstrukcji. Te konstrukcje obejmują system plików, połączenia sieciowe, kolekcje i tablice obiektów oraz daty. Typowy program w języku C# używa typów z biblioteki klas i typów zdefiniowanych przez użytkownika, które modelują koncepcje specyficzne dla domeny problemu programu.

Typy wbudowane

Język C# udostępnia standardowy zestaw wbudowanych typów. Te standardowe typy reprezentują liczby całkowite, wartości zmiennoprzecinkowe, wyrażenia logiczne, znaki tekstowe, wartości dziesiętne i inne typy danych. Istnieją również wbudowane typy ciągów i obiektów. Te typy są dostępne do użycia w dowolnym programie języka C#.

Typy niestandardowe

Do tworzenia własnych typów niestandardowych używa się konstrukcji struct, class, interface, enumi record. Sama biblioteka klas platformy .NET to kolekcja typów niestandardowych, których można używać we własnych aplikacjach. Domyślnie najczęściej używane typy w bibliotece klas są dostępne w dowolnym programie języka C#. Inne stają się dostępne tylko wtedy, gdy jawnie dodasz odwołanie do projektu do zestawu, który je definiuje. Gdy kompilator ma odwołanie do zestawu, można zadeklarować zmienne (i stałe) typów zadeklarowanych w tym zestawie w kodzie źródłowym.

Wspólny system typów

Ważne jest, aby zrozumieć dwa podstawowe kwestie dotyczące systemu typów na platformie .NET:

  • Popiera zasadę dziedziczenia. Typy mogą pochodzić z innych typów nazywanych typami podstawowymi. Typ pochodny dziedziczy (z pewnymi ograniczeniami) metody, właściwości i inne elementy członkowskie typu podstawowego. Typ podstawowy może z kolei pochodzić z innego typu, w którym przypadku typ pochodny dziedziczy elementy członkowskie obu typów bazowych w hierarchii dziedziczenia. Wszystkie typy, w tym wbudowane typy liczbowe, takie jak System.Int32 (słowo kluczowe C#: int), pochodzą ostatecznie z pojedynczego typu podstawowego, który jest System.Object (słowo kluczowe C#: object). Ta ujednolicona hierarchia typów nosi nazwę Common Type System (CTS).

  • Każdy typ w usłudze CTS jest definiowany jako typ wartości lub typ odwołania. Te typy obejmują wszystkie typy niestandardowe w bibliotece klas platformy .NET, a także własne typy zdefiniowane przez użytkownika. Typy definiowane za pomocą słowa kluczowego struktury to typy wartości; wszystkie wbudowane typy liczbowe są strukturami. Typy definiowane za pomocą klasy lub słowa kluczowego rekordu to typy odwołań. Typy referencyjne i typy wartości mają różne reguły czasu kompilacji i różne zachowanie w czasie wykonywania.

Poniższa ilustracja przedstawia relację między typami wartości i typami referencyjnymi w usłudze CTS.

Diagram przedstawiający typy wartości i odwołań.

Klasy i struktury to dwie podstawowe konstrukcje wspólnego systemu typów na platformie .NET. Każda jest zasadniczo strukturą danych, która zawiera zestaw danych i zachowania należące do niej jako całości. Dane i zachowania są członkami class, structlub record. Składowe klasy obejmują właściwości (dane), metody (zachowania), pola (zmienne zadeklarowane wewnątrz klasy) i wiele innych.

Deklaracja class, structlub record przypomina strategię używaną do tworzenia wystąpień lub obiektów w czasie wykonywania. Jeśli zdefiniujesz klasę o nazwie Person, Person jest nazwą typu. Jeśli deklarujesz i inicjujesz zmienną p typu Person, p mówi się, że jest obiektem lub wystąpieniem Person. Można utworzyć wiele wystąpień tego samego typu Person, a każde wystąpienie może mieć różne wartości we właściwościach i polach.

Klasa jest typem referencyjnym. Po utworzeniu obiektu typu zmienna, do której jest przypisany obiekt, zawiera tylko odwołanie do tej pamięci. Gdy odwołanie do obiektu zostanie przypisane do nowej zmiennej, nowa zmienna odwołuje się do oryginalnego obiektu. Zmiany wprowadzone za pomocą jednej zmiennej są odzwierciedlane w innej zmiennej, ponieważ obie odwołują się do tych samych danych.

struct jest typem wartości. Po utworzeniu struct zmienna, do której przypisano struct, przechowuje rzeczywiste dane struct. Po przypisaniu struct do nowej zmiennej wartość danych jest kopiowana. Nowa zmienna i oryginalna zmienna zawierają zatem dwie oddzielne kopie tych samych danych. Zmiany wprowadzone w jednej kopii nie mają wpływu na drugą kopię.

Typy rekordów mogą być typami referencyjnymi (record class) lub typami wartości (record struct). Typy rekordów zawierają metody, które obsługują równość wartości.

Ogólnie rzecz biorąc, klasy są używane do modelowania bardziej złożonego zachowania. Klasy zwykle przechowują dane, które mają być modyfikowane po utworzeniu obiektu klasy. Struktury najlepiej nadają się do małych struktur danych. Struktury zwykle przechowują dane, które nie mają być modyfikowane po utworzeniu struct. Typy rekordów to struktury danych z innymi składowymi syntetyzowanymi kompilatorem. Rekordy zwykle przechowują dane, które nie mają być modyfikowane po utworzeniu obiektu.

Typy wartości

Typy wartości pochodzą z System.ValueType, i te z kolei pochodzą z System.Object. Typy pochodzące z System.ValueType mają specjalne zachowanie w środowisku uruchomieniowym języka wspólnego. Zmienne typu wartości zawierają bezpośrednio swoje wartości. Pamięć dla struct jest przydzielana bezpośrednio w kontekście, w którym zmienna jest zadeklarowana. Nie ma oddzielnej alokacji sterty ani nakładu na zbieranie nieużytków dla zmiennych typu wartości. Można definiować typy struktur rekordów, które są typami wartości, i uwzględniać wszystkie syntetyzowane elementy dla rekordów.

Są dwie kategorie typów wartości: struct i enum.

Wbudowane typy liczbowe są strukturami i mają pola i metody, do których można uzyskać dostęp:


// constant field on type byte.
byte b = byte.MaxValue;

Jednak deklarujesz i przypisujesz do nich wartości tak, jakby były prostymi typami niegregowanymi:


byte num = 0xA;
int i = 5;
char c = 'Z';

Typy wartości są zapieczętowane. Nie można utworzyć typu z dowolnego typu wartości, na przykład System.Int32. Nie można zdefiniować struct, aby dziedziczyło po jakichkolwiek class zdefiniowanych przez użytkownika lub struct, ponieważ struct może dziedziczyć tylko po System.ValueType.

Słowo kluczowe struct służy do tworzenia własnych typów wartości. Zazwyczaj struktura jest używana jako kontener dla małego zestawu powiązanych zmiennych, jak pokazano w poniższym przykładzie:


public struct Coords
{
    public int x, y;

    public Coords(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}

Inną kategorią typów wartości jest enum. enum definiuje zestaw nazwanych stałych całkowitych. Na przykład wyliczenie System.IO.FileMode w bibliotece klas platformy .NET zawiera zestaw nazwanych stałych liczb całkowitych określający sposób otwierania pliku. Składnia enum jest pokazana w poniższym przykładzie:


public enum FileMode
{
    CreateNew = 1,
    Create = 2,
    Open = 3,
    OpenOrCreate = 4,
    Truncate = 5,
    Append = 6,
}

Stała System.IO.FileMode.Create ma wartość 2. Jednak nazwa jest znacznie bardziej sensowna dla ludzi odczytujących kod źródłowy i z tego powodu lepiej używać wyliczenia zamiast stałych literalnych liczb.

Wszystkie enums dziedziczą po System.Enum, która dziedziczy po System.ValueType. Wszystkie reguły stosowane do structs mają również zastosowanie do enums.

Typy odwołań

Typy class, record, delegate, arrayi interface to typy referencyjne .

Podczas deklarowania zmiennej typu odwołania zawiera ona wartość null, dopóki nie zostanie jej przypisana instancja tego typu lub dopóki nie utworzysz jej przy użyciu operatora new.

W poniższym przykładzie pokazano, jak zadeklarować zmienne typu odwołania przy użyciu tablic:


// Declaring an array variable
int[] numbers;

// Initializing the array with a size of 5
numbers = new int[5];

// Alternatively, declaring and initializing an array in one line
int[] numbers2 = new int[] { 1, 2, 3, 4, 5 };

// Assigning a reference to another variable
int[] numbers3 = numbers2;

Operator new tworzy wystąpienie typu i zwraca odwołanie do tego wystąpienia. Odwołanie to adres pamięci obiektu i odwołanie jest przechowywane w zmiennej. Podczas przypisywania zmiennej typu odwołania do innej zmiennej kopiujesz odwołanie, a nie sam obiekt. Obie zmienne odwołują się do tego samego obiektu w pamięci.

Notatka

Oprócz bycia typami referencyjnymi, tablice są kolekcjami. Kolekcje można zainicjować przy użyciu wyrażeń kolekcji, co eliminuje wymóg uwzględnienia słowa kluczowego new podczas deklarowania i inicjowania tablicy w jednym wierszu. Na przykład: int[] numbers = [ 1, 2, 3, 4, 5 ];.

Można utworzyć obiekt klasy za pomocą takiej samej składni, jak przy tworzeniu wbudowanych typów. W poniższym przykładzie pokazano, jak utworzyć wystąpienie klasy:


MyClass myClass = new MyClass();
MyClass myClass2 = myClass;

Operator new tworzy wystąpienie klasy i zwraca odwołanie do tego wystąpienia. Odwołanie to adres pamięci obiektu i odwołanie jest przechowywane w zmiennej. Podczas przypisywania zmiennej typu odwołania do innej zmiennej kopiujesz odwołanie, a nie sam obiekt. Obie zmienne odwołują się do tego samego obiektu w pamięci.