Zmienne w C# są podzielone na następujące rodzaje:
  • typy wartościowe (value types)
  • typy referencyjne (reference types)
  • typy wskaźnikowe (poiner types)
Typy wartościowe

Zmienne typu wartościowego mogą mieć bezpośrednio przypisaną wartość. Dziedziczą one z klasy System.ValueType.

Typy wartościowe zawierają dane. Niektóre z tych typów to: int, char, float, które pozwalają na składowanie numerów, liter czy liczb zmienno-przecinkowych. Kiedy deklarujemy wartościowy typ danych, system od razu przydziela pamięć potrzebną na przechowywanie tej zmiennej.

Poniższa tabela zawiera listę dostępnych typów wartościowych:
Typ Reprezentacja Zakres Domyślna wartość
bool typ logiczny true lub false false
byte 8-bitowa liczba całkowita 0 do 255 0
char 16-bitowy znak z tablicy Unicode U +0000 do U +ffff '\0'
decimal 128-bitowa wartość dziesiętna z dokładnością 28-29 cyfr znaczących (-7.9 x 1028 do 7.9 x 1028) / 100 do 28 0.0M
double 64-bitowa wartość o podwójnej precyzji używana do obliczeń zmiennoprzecinkowych (+/-)5.0 x 10-324 do (+/-)1.7 x 10308 0.0D
float 32-bitowa wartość o pojedynczej prezycji używana do obliczeń zmiennoprzecinkowych -3.4 x 1038 do + 3.4 x 1038 0.0F
int 32-bitowa liczba całkowita -2,147,483,648 do 2,147,483,647 0
long 64-bitowa liczba całkowita -9,223,372,036,854,775,808 do 9,223,372,036,854,775,807 0L
sbyte 8-bitowa liczba całkowita -128 do 127 0
short 16-bitowa liczba całkowita -32,768 do 32,767 0
uint 32-bitowa liczba całkowita (bez możliwości przechowywania liczb ujemnych) 0 do 4,294,967,295 0
ulong 64-bitowa liczba całkowita (bez możliwości przechowywania liczb ujemnych) 0 do 18,446,744,073,709,551,615 0
ushort 16-bitowa liczba całkowita (bez możliwości przechowywania liczb ujemnych) 0 do 65,535 0
W języku C# mamy możliwość sprawdzenia ile pamięci zajmuje dana zmienna. Należy w tym wypadku użyć metody sizeof(type). Zwrócona zostanie nam wartość zajętej pamięci przez określony typ danych. Wartość ta będzie podana w bajtach.

Oto przykład sprawdzenia rozmiaru w pamięci 32-bitowej liczby całkowitej:
using System;
namespace TypyDanych_WielkoscPamieci
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Rozmiar zmiennej typu int: {0}", sizeof(int));
            Console.ReadLine();
            // Wynik działania programu: Rozmiar zmiennej typu int: 4
            // Powyższa wartość wyrażona w bajtach -> 1 bajt to 8 bitów
            // Wynika zatem, że int zajmuje w pamięci 32 bity (zgodność z zaprezentowaną
            // wyżej tabelą.
        }
    }
}

Typy referencyjne

Typy referencyjne nie przechowują rzeczywistych wartości w zmiennej, przechowują referencje do zmiennych.

Innymi słowy, odnoszą się do konkretnej lokalizacji w pamięci. Przykładami wbudowanych typów referencyjnych są: obiekty (objects), typy dynamiczne (dynamics) oraz string (typ tekstowy).

Typ obiektowy (object type)
Typ obiektowy Object Type jest klasą bazową dla wszystkich typów danych w CTS (Common Type System) - jeden z bloków składowych platform .NET, który jest odpowiedzialny za opis wszystkich danych udostępnionych przez środowisko uruchomieniowe. Do typów obiektowych możemy przypisać dowolną wartość, tj. typ wartościowy, typ referencyjny oraz typ zdefiniowany przez użytkownika. Jednakże, przed przypisaniem wartości należy dokonać odpowiedniej konwersji.

W momencie gdy typ wartościowy jest konwertowany na typ obiektowy mamy doczynienia z operacją boxing. Jeżeli obiekt jest konwertowany na typ wartościowy mamy doczyniania z operacją unboxing.
// Najpierw boxing - konwertowanie typu wartosciowego na obiekt
int i = 10;
object o = i;
// A teraz zrobimy unboxing
int liczba = (int)o;

Typ dynamiczny (dynamic type)

W zmiennej dynamicznej możemy przechowywać dowolny typ danych. Sprawdzanie typów zmiennych odbywa się w momencie wykonywania programu.

Składnia typów dynamicznych jest następująca:
dynamic nazwa_zmiennej = value;

Typy dynamiczne są podobne do typów obiektowych z zaznaczeniem, że sprawdzanie typów dla typów obiektowych odbywa się w trakcie kompilacji, podczas gdy sprawadzenie typów dla typów dynamicznych odbywa się w trakcie wykonywania programu.

Typ łańcuchowy (String type)

Typ łańcuchowy String Type pozwala przypisać dowolną wartość tekstową do naszej zmiennej. Wartość może zostać przypisana na jeden z dwóch sposobów: cytowany (quoted) lub @cytowany ( @quoted ). Warto również nadmienić, że string to alias do klasy System.String. Dziedziczy on z typu obiektowego Object Type.
Przykład:
String tekst = "Witaj drogi Użytkowniku!";
Lub
String tekst = @"Witaj drogi Użytkowniku!";
A jaka w takim razie jest różnica w powyższych zapisach? W drugim przypadku użyliśmy znaku @, którego zadaniem jest wyłączenie sekwencji ucieczki. Co to dla nas oznacznia? Taki łańcuch znaków traktowany jest jako tzw. dosłowny string i uwzględnia wszystkie znaki takimi jakimi są. Użycie tego znaku znajduje zastosowanie np. podczas uzyskiwania dostępu do konkretnego folderu w systemie Windows.

Prosty przykład:
String lokalizacja_1 = "C:\Windows\System32"; // błąd o nierozpoznanej sekwnecji ucieczki
String lokalizacja_2 = @"C:\Windows\System32"; // dysponujemy aliasem do wskazanego folderu
Z czasem pojawi się u Was pytanie, czym się różni Sting vs string? string, tak jak wyżej zostało wspomniane to alias do System.String. Teoretycznie więc nie ma różnicy. Jako dobry przewodnik dla programistów i czytelności kodu warto zastosować się do poniższych wskazówek. string zaleca się używać jeżeli odnosimy do zmiennej:
string tekst = "Witaj";
Jeżeli jednak chcemy odwołać się do klasy, a ściślej metod w niej zaimplementowanych warto skorzystać z poniższego zapisu:
string Imie = "Paweł";
string greet = String.Format("Hello {0}!", Imie);
Typy referencyjne zdefiniowane przez użytkownika to: klasy, interfejsy oraz delagaty. Zostaną one omówione w późniejszych rozdziałach.

Typy wskaźnikowe

Zmienne typu wskaźnikowego przechowują w pamięci adres do danego typu. Wskaźniki w C# to po prostu zmienna, która przechowuje ten adres. W języku C# wskaźnik może trzymać adres jedynie dla typów wartościowych i tablic.

Przykład użycia:
int* ptr;

Deklarujemy wskaźnik zmiennej ptr która składuje adres dla zmiennej typu int. Operator odniesienia(reference operator) – ‘&’ może być użyty do pobrania adresu w pamięci.

int x = 100;

Wyrażenie &x zwraca nam adres pamięci zmiennej x, którą możemy przypisać do zmiennej wskaźnikowej.

int *ptr = &x;

Podsumowanie:
Console.WriteLine((int)ptr); // Wyświetla adres pamięci
Console.WriteLine(*ptr); // Wyświetla wartość przechowywaną w pamięci

Należy również zaznaczyć, że używanie wskaźników w języku C# wymaga użycia tzn. unsafe context. Polecenia takie wykonywane są poza kontrolą Garbage Collector.