Typy podstawowe

Typy podstawowe #

Słowa kluczowe typów wbudowanych (np. int, double, bool) są bezpośrednimi aliasami dla typów zdefiniowanych w przestrzeni nazw System (np. System.Int32, System.Double, System.Boolean). Warto pamiętać, że te typy to zwykłe klasy w bibliotece standardowej, w nich są zdefiniowane przydatne stałe i metody, np. int.MaxValue, int.Parse(string), float.NegativeInfinity.

Typy numeryczne #

TypNazwa .NETRozmiarZakres (przybliżony)PrecyzjaSufiks literału
sbyteSystem.SByte8 bitów-128 do 127--
byteSystem.Byte8 bitów0 do 255--
shortSystem.Int1616 bitów-32 768 do 32 767--
ushortSystem.UInt1616 bitów0 do 65 535--
intSystem.Int3232 bity-2,1×10⁹ do 2,1×10⁹--
uintSystem.UInt3232 bity0 do 4,2×10⁹-U lub u
longSystem.Int6464 bity-9×10¹⁸ do 9×10¹⁸-L lub l
ulongSystem.UInt6464 bity0 do 18×10¹⁸-UL lub ul
floatSystem.Single32 bity±1.5×10⁻⁴⁵ do ±3.4×10³⁸~6-9 cyfrF lub f
doubleSystem.Double64 bity±5.0×10⁻³²⁴ do ±1.7×10³⁰⁸~15-17 cyfrD lub d
decimalSystem.Decimal128 bitów±1.0×10⁻²⁸ do ±7.9×10²⁸28-29 cyfrM lub m

Konwersje numeryczne #

C# rozróżnia dwa rodzaje konwersji między typami numerycznymi.

Konwersje niejawne (Implicit) #

Są to konwersje bezpieczne, wykonywane automatycznie przez kompilator, gdy nie ma ryzyka utraty danych (z małymi wyjątkami). Konwersja jest możliwa z typu o mniejszym zakresie do typu o większym zakresie.

  • Z całkowitoliczbowych na całkowitoliczbowe: sbyteshortintlong
  • Z całkowitoliczbowych na zmiennoprzecinkowe: intfloat (ryzyko utraty precyzji) → double
  • long można niejawnie konwertować na float lub double (ryzyko utraty precyzji).
int i = 100;
long l = i;       // OK
float f = l;      // OK, ale może utracić precyzję dla dużych liczb
double d = f;     // OK

Konwersje jawne (Rzutowanie / Explicit) #

Wymagają świadomej decyzji programisty i użycia operatora rzutowania (typ). Stosuje się je, gdy istnieje ryzyko utraty informacji.

Konwersja z double lub float na typ całkowitoliczbowy powoduje obcięcie części ułamkowej.

Dla typów całkowitoliczbowych jeżeli operacja powoduje przepełnienie (wartość przekracza zakres typu docelowego), to domyślnym zachowaniem jest wykonanie operacji tak jakby była ona wykonana na typie większym i obcięcie bitów znaczących.

double d = 99.9;
int i = (int)d; // i = 99 (część ułamkowa obcięta)

long l = 3_000_000_000L;
int i2 = (int)l; // i2 = -1294967296 (przepełnienie)

Do kontroli przepełnienia służą konteksty checked i unchecked. W kontekście checked przepełnienie traktowane jest jako błąd.

// W bloku checked w przypadku przepełnienia rzucany jest wyjątek System.OverflowException
try
{
    checked
    {
        int i3 = (int)l;
    }
}
catch (OverflowException ex)
{
    Console.WriteLine(ex.Message);
}

Typ decimal #

Typu decimal należy używać do operacji finansowych i monetarnych, gdzie błędy zaokrągleń są niedopuszczalne.

  • decimal to typ zmiennoprzecinkowy o podstawie 10. W przeciwieństwie do float i double (podstawa 2), decimal dokładnie reprezentuje ułamki dziesiętne (np. 0.1, 0.2).
  • W pamięci jest przechowywany jako 96-bitowa mantysa (liczby całkowitej), bit znaku i 31 bitów wykładnika (potęgi 10, określającej pozycję przecinka).

Konwersje między decimal a float/double zawsze muszą być jawne.

Mimo swojej precyzji, decimal nie jest uniwersalnym rozwiązaniem i ma istotne wady w porównaniu do float i double:

  • Wydajność: Operacje na decimal są znacznie wolniejsze. Arytmetyka dla float i double jest wykonywana bezpośrednio przez procesor (w jednostce FPU), podczas gdy operacje na decimal są najczęściej realizowane programowo, co wiąże się z większym narzutem.
  • Mniejszy zakres: decimal ma znacznie mniejszy zakres wartości niż double. Nie nadaje się do obliczeń naukowych, gdzie operuje się na bardzo dużych lub bardzo małych liczbach.
  • Zużycie pamięci: Zajmuje 16 bajtów, czyli dwa razy więcej niż double (8 bajtów) i cztery razy więcej niż float (4 bajty).

Typ bool #

Typ bool (alias typu System.Boolean) reprezentuje wartości true i false. W pamięci zajmuje 1 bajt.

int x = 1;
bool flag = x > 0;
if (flag)
{
    // ...
    flag = false;
}

W odróżnieniu od C++ nie istnieją konwersje między bool a typami liczbowymi.

Operatory porównania #

Dla typów bezpośrednich operacja porównania domyślnie sprawdza czy obiekty są identyczne pole po polu.

Point p1 = new Point {X = 5, Y = 3};
Point p2 = p1; p2.X = 0;
Point p3 = new Point {X = -1, Y = 1};
Point p4 = new Point {X = -1, Y = 1};
Console.WriteLine(p1 == p2); // false
Console.WriteLine(p3 == p4); // true

public struct Point { public float X, Y; }

Dla typów referencyjnych operacja porównania domyślnie sprawdza czy referencje wskazują na ten sam obiekt.

Person p1 = new Person {Name = "Alice", Age = 30};
Person p2 = new Person {Name = "Alice", Age = 30};
Person p3 = new Person {Name = "Bob", Age = 25};
Person p4 = p3; p4.Age = 26;
Console.WriteLine(p1 == p2); // false
Console.WriteLine(p3 == p4); // true

public class Person { public string Name; public int Age; }

Operatory porównania mogą być nadpisane, tak, żeby zwracały dowolny typ. W praktyce nie ma to zbyt wiele sensu.

Operacje skrócone (Short-Circuiting) #

Operatory logiczne && i || wykonują tzw. operacje skrócone (Short-Circuiting). Na przykład, jeżeli lewa strona operatora && zewaluuje się do false, to jego prawa strona nie będzie ewaluowana. Dla odróżnienia operatory & i | powodują ewaluację obu stron operatora zawsze.

if (user != null && user.HasPermission("admin"))
{
    Console.WriteLine("Użytkownik ma uprawnienia admina.");
}

W tym przykładzie dzięki temu mechanizmowi unikamy NullReferenceException. Jeżeli użytkownik jest nullem, to nie wywoła się na nim metoda.

Typ char #

Typ char (alias typu System.Char) w .NET ma stały rozmiar 2 bajtów - to znak kodowany w UTF-16.

Typ char można niejawnie konwertować na inne typy liczbowe jeżeli ten typ pomieści w sobie ushort. W przeciwnym wypadku wymagana jest jawna konwersja.

char a = 'A';
char newLine = '\n';
char copyright = '\u00A9';

Klasa Convert #

Klasa Convert zawiera mnóstwo przydatnych metod do konwersji między typami. Daje możliwość konwersji między typami podstawowymi i stringami, czy konwersje między typem bool i liczbowymi. Konwersje z użyciem tej klasy zaokrąglają liczby zmiennoprzecinkowe przy konwersji do liczb całkowitoliczbowych.

int number = Convert.ToInt32("42");
int rounded = Convert.ToInt32(3.5);
bool isTrue = Convert.ToBoolean(1);
int binaryInt = Convert.ToInt32("101010", 2);
string hex = Convert.ToString(255, 16);