Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
C# to silnie typizowany język. Każda zmienna i stała mają typ, podobnie jak każde wyrażenie, które oblicza wartość. Każda deklaracja metody określa nazwę, typ i rodzaj (wartość, odwołanie lub dane wyjściowe) dla każdego parametru wejściowego i dla wartości zwracanej. Biblioteka klas platformy .NET definiuje wbudowane typy liczbowe i złożone, które reprezentują szeroką gamę konstrukcji. Obejmują one 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.
Informacje przechowywane w typie mogą zawierać następujące elementy:
- Pamięć, którą wymaga zmienna danego typu.
- Wartości maksymalne i minimalne, które mogą reprezentować.
- Członkowie (metody, pola, zdarzenia itd.), które zawiera.
- Typ podstawowy, z którego dziedziczy.
- Interfejsy, które implementuje.
- Dozwolone operacje.
Kompilator używa informacji o typie, aby upewnić się, że wszystkie operacje wykonywane w kodzie są bezpieczne. Jeśli na przykład zadeklarujesz zmienną typu int
, kompilator umożliwia używanie zmiennej w operacjach dodawania i odejmowania. Jeśli spróbujesz wykonać te same operacje na zmiennej typu bool
, kompilator generuje błąd, jak pokazano w poniższym przykładzie:
int a = 5;
int b = a + 2; //OK
bool test = true;
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
Uwaga / Notatka
Deweloperzy języka C i C++ powinni zauważyć, że w języku C# bool
nie jest konwertowalny na int
.
Kompilator osadza informacje o typie w pliku wykonywalnym jako metadane. Środowisko uruchomieniowe języka wspólnego (CLR) używa tych metadanych podczas działania, aby dodatkowo zagwarantować bezpieczeństwo typów podczas przydzielania i odzyskiwania pamięci.
Określanie typów w deklaracjach zmiennych
W przypadku deklarowania zmiennej lub stałej w programie należy określić jego typ lub użyć słowa kluczowego var
, aby umożliwić kompilatorowi wnioskowanie typu. W poniższym przykładzie przedstawiono niektóre deklaracje zmiennych, które używają zarówno wbudowanych typów liczbowych, jak i złożonych typów zdefiniowanych przez użytkownika:
// Declaration only:
float temperature;
string name;
MyClass myClass;
// Declaration with initializers (four examples):
char firstLetter = 'C';
var limit = 3;
int[] source = [0, 1, 2, 3, 4, 5];
var query = from item in source
where item <= limit
select item;
Typy parametrów metody i wartości zwracanych są określone w deklaracji metody. Poniższa sygnatura przedstawia metodę, która wymaga int
jako argumentu wejściowego i zwraca ciąg znaków.
public string GetName(int ID)
{
if (ID < names.Length)
return names[ID];
else
return String.Empty;
}
private string[] names = ["Spencer", "Sally", "Doug"];
Po zadeklarowaniu zmiennej nie można ponownie zadeklarować jej przy użyciu nowego typu i nie można przypisać wartości niezgodnej z jej zadeklarowanym typem. Na przykład nie można zadeklarować wartości , int
a następnie przypisać jej wartość logiczną true
. Można jednak przekonwertować wartości na inne typy, na przykład po przypisaniu ich do nowych zmiennych lub przekazaniu ich jako argumentów metody.
Konwersja typu, która nie powoduje utraty danych, jest wykonywana automatycznie przez kompilator. Konwersja, która może spowodować utratę danych, wymaga rzutowania w kodzie źródłowym.
Aby uzyskać więcej informacji, zobacz Rzutowanie i konwersje typów.
Typy wbudowane
Język C# udostępnia standardowy zestaw wbudowanych typów. Reprezentują one liczby całkowite, wartości zmiennoprzecinkowe, wyrażenia logiczne, znaki tekstowe, wartości dziesiętne i inne typy danych. Istnieją także wbudowane typy string
i object
. Te typy są dostępne do użycia w dowolnym programie języka C#. Aby uzyskać pełną listę wbudowanych typów, zobacz Wbudowane typy.
Typy niestandardowe
Do tworzenia własnych typów niestandardowych używa się konstrukcji struct
, class
, interface
, enum
i 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. Aby uzyskać więcej informacji, zobacz Biblioteka klas platformy .NET.
Jedną z pierwszych decyzji podejmowanych podczas definiowania typu jest podjęcie decyzji, która konstrukcja ma być używana dla danego typu. Poniższa lista ułatwia podjęcie tej początkowej decyzji. Opcje się pokrywają. W większości scenariuszy więcej niż jedna opcja jest rozsądnym wyborem.
- Jeśli rozmiar magazynu danych jest mały, nie więcej niż 64 bajty, wybierz wartość
struct
lubrecord struct
. - Jeśli typ jest niezmienny lub chcesz mutacji niezniszczającej, wybierz
struct
wartość lubrecord struct
wartość. - Jeśli Twój typ powinien mieć semantykę wartościową dla równości, wybierz
record class
lubrecord struct
. - Jeśli typ jest używany głównie do przechowywania danych, a nie zachowania, wybierz element
record class
lubrecord struct
. - Jeśli typ jest częścią hierarchii dziedziczenia, wybierz
record class
lubclass
. - Jeśli typ używa polimorfizmu, wybierz wartość
class
. - Jeśli podstawowym celem jest zachowanie, wybierz wartość
class
.
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 jednego typu podstawowego ( System.Object słowo kluczowe C#:object
). Ta ujednolicona hierarchia typów nosi nazwę Common Type System (CTS). Aby uzyskać więcej informacji na temat dziedziczenia w języku C#, zobacz Dziedziczenie. - 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
struct
to typy wartości. Wszystkie wbudowane typy liczbowe tostructs
. Typy definiowane za pomocą słowa kluczowegoclass
lubrecord
są typami referencyjnymi. 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.
Uwaga / Notatka
Widać, że najczęściej używane typy są zorganizowane w System przestrzeni nazw. Jednak przestrzeń nazw, w której znajduje się typ, nie ma relacji z typem wartości lub typem odwołania.
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ą elementami członkowskimi klasy, struktury lub rekordu. Członkowie obejmują metody, właściwości, zdarzenia itd., jak wymieniono w dalszej części tego artykułu.
Deklaracja klasy, struktury lub rekordu przypomina strategię używaną do tworzenia wystąpień lub obiektów w czasie wykonywania. Jeśli zdefiniujesz klasę, strukturę lub rekord 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.
Struktura jest typem wartości. Po utworzeniu struktury zmienna, do której przypisano strukturę, przechowuje rzeczywiste dane struktury. Gdy struktura zostanie przypisana do nowej zmiennej, zostanie skopiowana. 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 struktury. Typy rekordów to struktury danych z dodatkowymi 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 CLR. Zmienne typu wartości zawierają bezpośrednio swoje wartości. Pamięć dla struktury 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 zadeklarować record struct
typy wartości i uwzględnić syntetyzowane składowe dla rekordów.
Istnieją 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ć struktury tak, aby dziedziczyła z dowolnej klasy lub struktury zdefiniowanej przez użytkownika, ponieważ struktura może dziedziczyć tylko z System.ValueType. Jednak struktura może implementować jeden lub więcej interfejsów. Typ struktury można rzutować na dowolny typ interfejsu, który implementuje. Rzutowanie powoduje, że operacja boksowania opakowuje strukturę wewnątrz obiektu typu odwołania na zarządzanym stercie. Operacje boxingu są wykonywane podczas przekazywania typu wartości do metody, która jako parametr wejściowy przyjmuje typ System.Object lub dowolny typ interfejsu. Aby uzyskać więcej informacji, zobacz Boxing i Unboxing.
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;
}
}
Aby uzyskać więcej informacji na temat struktur, zobacz Typy struktury. Aby uzyskać więcej informacji na temat typów wartości, zobacz Typy wartości.
Inną kategorią typów wartości jest enum
. Wyliczenie 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. Jest on zdefiniowany tak, jak pokazano 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. Aby uzyskać więcej informacji, zobacz System.IO.FileMode.
Wszystkie wyliczenia dziedziczą po System.Enum, który dziedziczy po System.ValueType. Wszystkie reguły, które mają zastosowanie do struktur, mają również zastosowanie do typów wyliczeniowych. Aby uzyskać więcej informacji na temat wyliczeń, zobacz Typy wyliczeń.
Typy referencji
Typ, który jest zdefiniowany jako class
, record
, delegate
, tablica lub interface
, jest reference type
.
Podczas deklarowania zmiennej określonego typu reference type
, zawiera ona wartość null
do momentu przypisania jej z instancją tego typu lub utworzenia za pomocą operatora new
. Tworzenie i przypisywanie klasy przedstawiono w poniższym przykładzie:
MyClass myClass = new MyClass();
MyClass myClass2 = myClass;
Nie można bezpośrednio zainicjować interface
przy użyciu operatora new
. Zamiast tego utwórz i przypisz wystąpienie klasy, która implementuje interfejs. Rozważmy następujący przykład:
MyClass myClass = new MyClass();
// Declare and assign using an existing value.
IMyInterface myInterface = myClass;
// Or create and assign a value in a single statement.
IMyInterface myInterface2 = new MyClass();
Po utworzeniu obiektu pamięć jest przydzielana na zarządzanym stercie. Zmienna zawiera tylko odwołanie do lokalizacji obiektu. Typy na zarządzanym stercie wymagają kosztów dodatkowych zarówno podczas ich przydzielania, jak i odzyskiwania. Czyszczenie pamięci to funkcja automatycznego zarządzania pamięcią środowiska CLR, która wykonuje oczyszczanie. Jednak odzyskiwanie pamięci jest również wysoce zoptymalizowane i w większości scenariuszy nie powoduje problemu z wydajnością. Aby uzyskać więcej informacji na temat zbierania nieużytków, zobacz Automatyczne zarządzanie pamięcią.
Wszystkie tablice są typami referencyjnymi, nawet jeśli ich elementy są typami wartości. Tablice niejawnie pochodzą z System.Array klasy . Deklarujesz je i używasz z uproszczoną składnią dostarczaną przez język C#, jak pokazano w poniższym przykładzie:
// Declare and initialize an array of integers.
int[] nums = [1, 2, 3, 4, 5];
// Access an instance property of System.Array.
int len = nums.Length;
Typy referencyjne w pełni obsługują dziedziczenie. Podczas tworzenia klasy można dziedziczyć z dowolnego innego interfejsu lub klasy, która nie jest zdefiniowana jako zapieczętowana. Inne klasy mogą dziedziczyć z twojej klasy i przesłaniać twoje metody wirtualne. Aby uzyskać więcej informacji na temat tworzenia własnych klas, zobacz Klasy, struktury i rekordy. Aby uzyskać więcej informacji na temat dziedziczenia i metod wirtualnych, zobacz Dziedziczenie.
Rodzaje wartości literałów
W języku C# literały otrzymują przypisany typ przez kompilator. Można określić, jak należy wpisać literał liczbowy, dołączając literę na końcu liczby. Na przykład, aby określić, że wartość 4.56
powinna być traktowana jako float
, dołącz "f" lub "F" po liczbie: 4.56f
. Jeśli litera nie zostanie dołączona, kompilator wywnioskuje typ literału. Aby uzyskać więcej informacji na temat typów, które można określić z sufiksami literowymi, zobacz Typy liczb całkowitych i Typy liczb zmiennoprzecinkowych.
Ponieważ literały są typizowane, a wszystkie typy ostatecznie pochodzą z klasy System.Object, można napisać i skompilować kod, taki jak następujący kod:
string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);
Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);
Typy ogólne
Typ można zadeklarować przy użyciu co najmniej jednego parametru typu , który służy jako symbol zastępczy rzeczywistego typu ( typ betonowy). Kod klienta udostępnia konkretny typ podczas tworzenia wystąpienia typu. Takie typy są nazywane typami ogólnymi. Na przykład typ System.Collections.Generic.List<T> platformy .NET ma jeden parametr typu, który według konwencji ma nazwę T
. Podczas tworzenia wystąpienia typu należy określić typ obiektów, które zawiera lista, na przykład string
:
List<string> stringList = new List<string>();
stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);
Użycie parametru typu umożliwia ponowne użycie tej samej klasy w celu przechowywania dowolnego typu elementu bez konieczności konwertowania każdego elementu na obiekt. Klasy kolekcji ogólnych są nazywane silnie typiowanymi kolekcjami , ponieważ kompilator zna konkretny typ elementów kolekcji i może zgłosić błąd w czasie kompilacji, jeśli na przykład spróbujesz dodać liczbę całkowitą do stringList
obiektu w poprzednim przykładzie. Aby uzyskać więcej informacji, zobacz Generyki.
Typy niejawne, typy anonimowe i typy wartości dopuszczające null
Możesz niejawnie wpisać zmienną lokalną (ale nie składową klasy), używając słowa kluczowego var
. Zmienna nadal otrzymuje typ w czasie kompilacji, ale typ jest dostarczany przez kompilator. Aby uzyskać więcej informacji, zobacz Niejawnie wpisane zmienne lokalne.
Może to być niewygodne, aby utworzyć nazwany typ dla prostych zestawów powiązanych wartości, których nie zamierzasz przechowywać ani przekazywać poza granice metod. W tym celu można tworzyć typy anonimowe . Aby uzyskać więcej informacji, zobacz Typy anonimowe.
Zwykłe typy wartości nie mogą mieć wartości null
. Można jednak utworzyć typy wartości dopuszczających wartość null , dołączając wartość ?
po typie. Na przykład int?
jest typem int
, który może również mieć wartość null
. Typy wartości dopuszczalnych są wystąpieniami ogólnego typu struktury System.Nullable<T>. Typy wartości nullable są szczególnie przydatne podczas przekazywania danych do i z baz danych, w których wartości liczbowe mogą być null
. Aby uzyskać więcej informacji, zobacz Typy wartości dopuszczających null.
Typ czasu kompilacji i typ czasu wykonywania
Zmienna może mieć różne typy czasu kompilacji i czasu wykonywania. Typ czasu kompilacji to zadeklarowany lub wywnioskowany typ zmiennej w kodzie źródłowym. Typ czasu wykonywania to typ wystąpienia, do którego odwołuje się ta zmienna. Często te dwa typy są takie same, jak w poniższym przykładzie:
string message = "This is a string of characters";
W innych przypadkach typ czasu kompilacji jest inny, jak pokazano w następujących dwóch przykładach:
object anotherMessage = "This is another string of characters";
IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";
W obu powyższych przykładach typ czasu wykonywania to string
. Typ czasu kompilacji znajduje się object
w pierwszym wierszu i IEnumerable<char>
w drugim.
Jeśli dwa typy są różne dla zmiennej, ważne jest, aby zrozumieć, kiedy typ czasu kompilacji i typ czasu wykonywania mają zastosowanie. Typ czasu kompilacji określa wszystkie akcje wykonywane przez kompilator. Te akcje kompilatora obejmują rozpoznawanie wywołań metod, rozpoznawanie przeciążeń oraz dostępne niejawne i jawne rzutowania. Typ wykonywania w czasie rzeczywistym określa wszystkie akcje, które są rozwiązywane podczas wykonywania. Te działania w czasie wykonywania obejmują wysyłanie wywołań metod wirtualnych, ewaluację wyrażeń is
i switch
, a także inne interfejsy API testowania typów. Aby lepiej zrozumieć sposób interakcji kodu z typami, należy rozpoznać, która akcja ma zastosowanie do jakiego typu.
Powiązane sekcje
Aby uzyskać więcej informacji, zobacz następujące artykuły:
Specyfikacja języka C#
Aby uzyskać więcej informacji, zobacz Specyfikacja języka C#. Specyfikacja języka jest ostatecznym źródłem informacji o składni i użyciu języka C#.