Информация о полезной статье

RAM and Data Types (Типы данных и память)

Структура типов данных C#

RAM and Data Types
│
├─ Stack (value types - типы значений)
│  ├─ enum (перечисления)
│  ├─ Tuple (картежи)
│  ├─ Nullable value types [C# 2.0+]
│  └─ Structure (структуры)
│     ├─ struct
│     ├─ ref struct [C# 7.2+]
│     ├─ bool
│     ├─ char
│     └─ Numeric types (числовые типы)
│        ├─ Signed Integers (целочисленные отрицательные)
│        │  ├─ sbyte
│        │  ├─ short
│        │  ├─ int
│        │  └─ long
│        ├─ Unsigned Integers (целочисленные положительные)
│        │  ├─ byte
│        │  ├─ ushort
│        │  ├─ uint
│        │  └─ ulong
│        ├─ System-dependent Integers (зависимые от разряда системы)
│        │  ├─ nint
│        │  └─ nuint
│        └─ Floating-point Numbers (с плавающей точкой)
│           ├─ float
│           ├─ double
│           └─ decimal
│
└─ Heap (reference types - типы ссылок)
   ├─ object
   ├─ class
   ├─ record [C# 9+]
   ├─ interface
   ├─ delegate
   ├─ dynamic
   ├─ string
   └─ array (массив)
   └─ Nullable reference types [C# 8+]

(Unsafe контекст)
└─ Pointer types (указатели) - переменные в Stack, указывают на любую область памяти
   ├─ Указатели на типы значений: int*, long*, short*, byte*, bool*, char*, float*, double*, decimal*
   ├─ void* - универсальный указатель (может указывать на любой тип)
   │  └─ Могут указывать на: Stack, Heap, неуправляемую память
   └─ Function pointers (delegate*) - указатели на функции

Signed Integers (целочисленные отрицательные):

  • sbyte - 8 бит, -128 до 127
  • short - 16 бит, -32,768 до 32,767
  • int - 32 бит, -2,147,483,648 до 2,147,483,647
  • long - 64 бит, -9,223,372,036,854,775,808 до 9,223,372,036,854,775,807

Unsigned Integers (целочисленные положительные):

  • byte - 8 бит, 0 до 255
  • ushort - 16 бит, 0 до 65,535
  • uint - 32 бит, 0 до 4,294,967,295
  • ulong - 64 бит, 0 до 18,446,744,073,709,551,615

System-dependent Integers (зависимые от разряда системы):

  • nint - нативное целое число (размер зависит от платформы)
  • nuint - нативное беззнаковое целое число

Floating-point Numbers (с плавающей точкой):

  • float - 32 бит, одинарная точность
  • double - 64 бит, двойная точность
  • decimal - 128 бит, высокая точность для финансовых расчетов

Важные нюансы размещения в памяти

1. Типы значений (Value Types) не всегда в Stack

Важно: Типы значений хранятся в Stack только когда они являются локальными переменными.

  • В Stack: локальные переменные, параметры методов
  • В Heap: поля классов/структур, элементы массивов
class MyClass
{
    int value;  // В Heap (как часть объекта MyClass)
}

void Method()
{
    int local = 42;  // В Stack (локальная переменная)
    int[] array = new int[10];
    array[0] = 1;    // В Heap (элемент массива)
}

2. Tuple vs ValueTuple

  • Tuple (class) - ссылочный тип, хранится в Heap (устаревший, не рекомендуется)
  • ValueTuple (struct) - тип значения, хранится в Stack (рекомендуется)
// ValueTuple - в Stack
(int, string) tuple1 = (1, "hello");

// Tuple - в Heap (устаревший)
Tuple<int, string> tuple2 = Tuple.Create(1, "hello");

3. Boxing и Unboxing

Boxing (преобразование-упаковка)

При перемещении данных из типов значения в ссылочные типы, то есть из стека в кучу.

Преобразование: int -> object

int number = 420;
object obj = number;

UnBoxing (преобразование-распаковка)

Обратное действие из ссылочных типов в тип значения, то есть из кучи в стек.

Преобразование: object -> int

object obj = 420;
int number = (int) obj;

Производительность: Boxing/Unboxing создает копии и влияет на производительность!

4. ref struct ограничения

ref struct должны находиться только в Stack и имеют ограничения:

  • Не могут быть полями обычных классов или структур
  • Не могут быть элементами массивов
  • Не могут быть generic параметрами (кроме других ref struct)
  • Не могут реализовывать интерфейсы
  • Используются для высокопроизводительных типов: Span<T>, ReadOnlySpan<T>

5. String Interning (интернирование строк)

Строки в C# могут быть интернированы - одинаковые строковые литералы могут указывать на один объект в Heap:

string s1 = "Hello";
string s2 = "Hello";
// s1 и s2 могут указывать на один объект (зависит от компилятора и runtime)
bool same = ReferenceEquals(s1, s2);  // Может быть true

6. Nullable Value Types

Важно: Nullable<T> (или T?) - это структура, которая хранит:

  • Значение типа T
  • Флаг HasValue (было ли присвоено значение)

Размер: размер базового типа + 1 байт (обычно выравнивается до размера базового типа + sizeof(bool))

int? nullable = null;  // В Stack: структура Nullable<int> с HasValue = false
int? nullable2 = 42;   // В Stack: структура Nullable<int> с HasValue = true, Value = 42

7. Массивы - элементы в Heap

Массив - ссылочный тип, хранится в Heap. Элементы массива тоже хранятся в Heap (даже если это типы значений):

int[] numbers = new int[10];  // Массив в Heap
numbers[0] = 42;              // Элемент массива тоже в Heap (не в Stack!)

8. Dynamic - это object

Тип dynamic в runtime является object:

  • Проверка типов откладывается до выполнения (runtime)
  • Может привести к ошибкам времени выполнения
  • Используется для работы с COM, JSON, динамическими языками

9. Размер указателей

  • В 32-битных системах: 4 байта
  • В 64-битных системах: 8 байт
  • Размер не зависит от типа, на который указывает указатель

10. ref локальные переменные и ref return

С C# 7.0 можно возвращать ссылки (не копии):

ref int GetReference(int[] array) => ref array[0];

int[] arr = { 1, 2, 3 };
ref int first = ref GetReference(arr);
first = 100;  // Изменит arr[0] напрямую (без копирования)

Важно: ref переменные - это алиасы (псевдонимы), не новые переменные в памяти.