Типы данных в C#
Как выбирать тип под задачу
Быстрый ориентир:
int,long- счётчики, идентификаторы, индексы;decimal- деньги и финансовые расчёты;double- инженерные/научные вычисления;bool- состояния и флаги;string- текст;DateTimeOffset- момент времени с учётом смещения;Guid- глобально уникальный идентификатор (ключ сущности, correlation id);class— сложная модель с поведением и наследованием;struct— компактный значимый тип (координаты, ключ, интервал).
Практические советы по надёжности
- Для публичных контрактов API заранее решайте, где допустим
null. - Не смешивайте
doubleиdecimalв одной финансовой формуле. - Явно документируйте единицы измерения (
seconds,ms,bytes) прямо в названии переменной.
Типичные ошибки
- Использование
doubleдля цен. - Присваивание "магических чисел" без
enum/констант. - Объединение слишком разнородных данных в
objectбез нужды.
Смежные статьи
- Переменные и их области видимости
- Передача параметров в C# — ref, out, in
- Преобразование типов и система типизации
- Работа с типами
Типы данных в C#
Разработчику АрхитекторуДальше: Работа с типами — строки, коллекции, преобразования · Преобразование типов · Справочник C#
Типы данных
C# — статически типизированный язык с проверкой на этапе компиляции; модель сильная (неявные преобразования ограничены правилами языка).
С dynamic и object возможны участки с проверкой в runtime.
Теория — типы данных, типизация; преобразования — Преобразование типов.
Типы данных в C#
★ Типы данных – это фундамент любого языка программирования.
В C# типы делятся на несколько категорий:
- простые (примитивные) типы;
- ссылочные типы;
- структуры или значимые типы (value types);
- кортежи;
- пользовательские типы.
Основная конструкция, влияющая на определение типа, это конечно объявление переменной:
<тип> <имя> = <значение>;
Тип указывает категорию данных, имя предоставляет идентификатор для обращения, значение инициализирует переменную конкретными данными.
<тип> <имя>;
Play ITЗагрузка интерактивного демо…
Простые типы
| Тип | Описание | Размер |
|---|---|---|
int | Целое число | 4 байта |
long | Длинное целое | 8 байт |
short | Короткое целое | 2 байта |
byte | Число от 0 до 255 | 1 байт |
float, double, decimal | Числа с плавающей точкой | разный |
char | Символ Unicode | 2 байта |
string | Строка символов | динамический |
bool | Логическое значение (true / false) | — |
Play ITЗагрузка интерактивного демо…
Ссылочные и значимые типы
Ссылочные и значимые типы отличаются способом хранения данных в памяти и поведением при присваивании, передаче в методы и управлении ресурсами. Примеры для чисел, объектов и модификаторов ref, out, in — в статье Передача параметров в C#.
★ Значимые типы (Value Types) хранят непосредственно данные, а не ссылку на них. Обычно они размещаются в стеке, но могут быть частью объектов в куче, если входят в состав ссылочного типа.
Примеры:
int age = 30;
bool isStudent = true;
char grade = 'A';
DateTime birthDate = new DateTime(1995, 5, 20);
Разбор:
int age = 30;хранит целочисленное значение возраста.bool isStudent = true;задаёт логический флаг состояния (да/нет).char grade = 'A';демонстрирует одиночный символ Unicode в одинарных кавычках.new DateTime(1995, 5, 20)создаёт значение даты из года, месяца и дня.- Все переменные в примере имеют конкретные статические типы, проверяемые компилятором.
Примитивные (простые) типы также относятся к значимым типам (int, float, double, char и т.д.).
Помимо них, есть:
перечисления (enum)
enum Days { Monday, Tuesday, Wednesday }
Разбор:
enum Daysобъявляет перечисление с ограниченным набором допустимых значений.Monday,Tuesday,Wednesday— именованные константы, обычно с базовыми числовыми значениями0,1,2.- Перечисления повышают читаемость кода и уменьшают количество "магических чисел".
- Тип
Daysможно использовать вswitchи проверках бизнес-состояний.
Перечисление определяет именованный набор константных значений, представляющих логически связанные состояния или категории.
Объявление перечисления:
record <имя>(<тип> <параметр1>, <тип> <параметр2>)
структуры (struct)
Структура (struct) — способ описать свой тип данных в C#. Внутри можно объявить поля, свойства и методы — как в классе. Разница в том, как тип хранится в памяти и что происходит при копировании.
Ключевые термины:
- Значимый тип (value type) — переменная хранит сами данные (число, дата, координаты). Примитивы
int,boolи пользовательскиеstructотносятся к этой категории. Подробнее — ниже и статья Стек и куча. - Ссылочный тип (reference type) — переменная хранит ссылку (адрес) на объект в куче. Классы, строки, массивы и списки — ссылочные типы.
- Куча (heap) — область памяти для объектов с непредсказуемым временем жизни; память освобождает сборщик мусора (GC).
- Стек (stack) — область памяти для локальных переменных и параметров метода; очищается автоматически при выходе из метода.
- Неизменяемый тип (immutable) — после создания поля не меняются. Для
structэто рекомендуемый подход; помогают модификаторreadonly, свойства только для чтения и record struct. - Интерфейс — контракт с набором членов без реализации. И
class, иstructмогут его реализовать; наследование классов уstructнедоступно. - Boxing — упаковка значимого типа в объект в куче при присвоении переменной типа
objectили интерфейсу. См. значимые типы и boxing.
public readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public double DistanceTo(Point other) =>
Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2));
}
Разбор:
readonly struct— поля и свойства нельзя изменить после создания экземпляра; при копировании по значению это снижает риск случайно изменить копию вместо оригинала.- Конструктор задаёт начальные координаты.
- Метод
DistanceToпоказывает, что уstructможет быть поведение, как у класса. - Два поля
intзанимают 8 байт — типичный размер дляstruct.
Синтаксис объявления: struct Имя { ... }. Тип неявно запечатан (sealed) — от него нельзя наследоваться. Реализовать можно несколько интерфейсов.
Связанные материалы по ООП — class и struct в C#.
Отличия class и struct
class | struct | |
|---|---|---|
| Категория | Ссылочный тип | Значимый тип |
| Где лежат данные | Объект в куче; в переменной — ссылка | Данные внутри переменной; локальная переменная обычно на стеке |
Присваивание b = a | Две переменные указывают на один объект | Создаётся вторая копия всех полей |
| Значение по умолчанию | null (пустая ссылка) | Экземпляр с нулями в полях (default) |
| Наследование | Один базовый класс и интерфейсы | Только интерфейсы |
| Конструктор без параметров | Разрешён явно | До C# 10 явный parameterless запрещали; с C# 10+ — если все поля инициализированы (версии C#) |
| Изменение после создания | Любое; все ссылки видят изменение | Осторожно с изменяемыми полями — см. примеры |
Сравнение == | По ссылке, если не переопределён Equals | По полям, если не переопределён Equals |
// class — одна машина, два имени
var car1 = new Car { Model = "Tesla" };
var car2 = car1;
car2.Model = "BMW";
Console.WriteLine(car1.Model); // BMW
// struct — две отдельные точки
var p1 = new Point(1, 2);
var p2 = p1;
p2 = p2 with { X = 10 }; // выражение with (C# 9+) — новая копия с другим X
Console.WriteLine(p1.X); // 1
Чтобы изменить struct внутри метода и сохранить результат снаружи, передайте параметр по ссылке — ref, out, in — или верните новое значение из метода.
Типы в стандартной библиотеке .NET
BCL (Base Class Library) — встроенные типы платформы .NET. Часть из них объявлена как struct, часть как class. От категории зависит копирование и работа с null.
Значимые (struct)
- целые и дробные числа —
int,long,byte,bool,char,decimal,double,float(внутри этоSystem.Int32,System.Booleanи т.д.); - дата и время —
DateTime,DateTimeOffset,TimeSpan; - идентификаторы —
Guid; - отсутствие значения у примитива —
Nullable<T>(int?); - кортежи —
ValueTuple, синтаксис(int, string); - математика в играх —
System.Numerics.Vector2,Matrix4x4; - компактные записи —
record struct(C# 10+).
Ссылочные (class)
- текст и корень иерархии —
string,object; - коллекции и массивы —
List<T>,Dictionary<,>,int[]; - ошибки и доменные сущности —
Exception, ваши классы сервисов и моделей; - делегаты —
Action,Func; - изменяемый текст в цикле —
StringBuilder; - DTO по ссылке —
record(class), анонимные типы.
В Java у каждого примитива есть отдельный класс-обёртка (Integer для int). В C# примитив и есть структура — отдельный класс-обёртка для каждой переменной не нужен. Сравнение — значимые типы и boxing, типы в Java.
Проверка в коде:
Console.WriteLine(typeof(DateTime).IsValueType); // True
Console.WriteLine(typeof(string).IsValueType); // False
Как выбрать class или struct
Перед объявлением типа задайте три вопроса.
1. Размер данных
- Для
structориентир — до 16 байт (дваdoubleили триint). - При каждом вызове метода без
ref/inкопируются все поля целиком. - Большой набор полей удобнее оформить как
class— копируется только ссылка (4 или 8 байт).
2. Изменяемость
structчаще делают неизменяемым —readonly struct,record struct, методы, возвращающие новый экземпляр.- Если поля меняются после создания, проще ошибиться: метод получает копию, а не оригинал (см. примеры).
3. Иерархия типов
- Цепочки вроде
Animal→Dog— только черезclassи полиморфизм. - Общий контракт без общего базового класса — интерфейс; его реализуют и
class, иstruct.
| Задача | Подходящий тип |
|---|---|
| Координаты, цвет RGB, сумма с валютой | readonly struct или record struct |
Сервис, репозиторий, сущность БД, MonoBehaviour | class |
Ключ в Dictionary или HashSet | record struct или readonly struct с Equals/GetHashCode |
| Список разных подтипов в одной коллекции | class + наследование или интерфейсы |
При сомнении начните с class. Переходите на struct, когда тип маленький, редко меняется и профилировщик показывает лишнюю нагрузку на GC.
Примеры struct
Координаты и векторы
public readonly struct Vector2
{
public float X { get; }
public float Y { get; }
public Vector2(float x, float y) => (X, Y) = (x, y);
public Vector2 Add(Vector2 other) => new(X + other.X, Y + other.Y);
}
Цвет RGB — важно содержимое полей, отдельная "личность" объекта не нужна:
public readonly record struct RgbColor(byte R, byte G, byte B)
{
public static RgbColor White => new(255, 255, 255);
}
Интерфейс без наследования класса
public interface IArea
{
double Area { get; }
}
public readonly struct Circle : IArea
{
public double Radius { get; }
public Circle(double radius) => Radius = radius;
public double Area => Math.PI * Radius * Radius;
}
Переменная типа Circle хранит значение на стеке. При присвоении IArea shape = new Circle(5) сработает boxing — значение копируется в объект в куче. Для горячего кода предпочтительнее обобщения (where T : struct) или иерархия классов.
Изменяемый struct и параметр метода
public struct MutablePoint { public int X; public int Y; }
void MoveRight(MutablePoint p) => p.X++; // меняется локальная копия параметра
var origin = new MutablePoint { X = 0, Y = 0 };
MoveRight(origin);
Console.WriteLine(origin.X); // 0
void MoveRightRef(ref MutablePoint p) => p.X++; // меняется исходная переменная
Подробнее о ref — Переменные и области видимости.
Struct в List<T>
var points = new List<MutablePoint> { new() { X = 1, Y = 2 } };
points[0].X = 5; // ошибка компиляции: индексатор возвращает копию
var p = points[0];
p.X = 5;
points[0] = p; // записать изменённую копию обратно
С коллекциями удобнее работать через неизменяемый struct и выражение with.
Краткая шпаргалка
struct— компактные данные (точка, интервал, ключ сортировки), чаще неизменяемые.class— объекты с идентичностью, наследованием и долгим жизненным циклом.- Память и копирование — Стек и куча.
- ООП-контекст — class и struct в C#.
- Синтаксис
record,readonly,with— справочник C#.
ValueTuple
var person = (name: "Alice", age: 25);
Разбор:
- Создаётся кортеж (
ValueTuple) с именованными элементамиnameиage. varвыведет тип как(string name, int age).- К элементам можно обращаться по именам (
person.name,person.age), что читабельнееItem1/Item2. - Кортежи подходят для возврата нескольких связанных значений без отдельного класса.
При присваивании одного экземпляра значимого типа другому, копируются данные целиком:
int a = 10;
int b = a; // b получает копию значения a
a = 20;
Console.WriteLine(b); // Выведет 10
Разбор:
b = aкопирует значение, а не ссылку: каждая переменная хранит собственную копию числа.- После изменения
aна20переменнаяbостаётся прежней (10). - Это типичное поведение значимых типов (value types) при присваивании.
- Пример наглядно показывает, почему изменение одной переменной не влияет на другую.
Особенности значимых типов:
- более быстрые операции за счёт хранения в стеке;
- не поддерживают множественное наследование;
- не могут быть
null, если не объявлены как nullable (int?,bool?и т.д.);
nullable
Знак вопроса после типа позволяет переменной принимать значение null в дополнение к обычным значениям типа. Применяется к значимым типам.
<тип>? <имя> = <значение>;
Пример nullable-типа:
int? nullableInt = null;
Разбор:
int?— сокращённая формаNullable<int>, позволяющая хранить либо число, либоnull.- Присваивание
nullобозначает отсутствие значения, а не число0. - Это удобно для полей, где значение может быть неизвестно (например, необязательный возраст).
- Перед использованием такого значения обычно проверяют
HasValueили сравнивают сnull.
Nullable-версии типов могут принимать значение null, а внутри реализуются через обобщённую структуру System.Nullable<T>.
Значимые типы, boxing и аналог обёрток Java
В Java у каждого примитива есть отдельный класс-обёртка (int ↔ Integer, bool ↔ Boolean). Подробнее — Типы данных в Java — примитив и обёртка.
В C# отдельных классов Int32-обёрток для каждой переменной нет — значимые типы (int, double, bool, …) — это структуры, они хранятся по значению. Когда значимый тип нужен как объект (коллекция до generic-оптимизаций, object, интерфейс), компилятор выполняет boxing — копирует значение в объект в куче:
int count = 42; // значение в стеке (или внутри другого объекта)
object boxed = count; // boxing: int → объект в куче, ссылка в boxed
int again = (int)boxed; // unboxing: копия обратно в int
Java int / Integer | C# int / boxing | |
|---|---|---|
| Базовый тип | Примитив int | Значимый тип struct (синоним Int32) |
| "Обёртка" | Явный класс Integer | Объект создаётся при boxing в object |
| Nullable "нет числа" | Integer x = null | int? x = null (Nullable<int>) |
| Коллекции | List<Integer> | List<int> — без boxing на каждой операции |
| Сравнение | equals() для объектов | == для int; для nullable — HasValue |
int? (или Nullable<int>) — ближайший аналог "обёртки с null" из Java — это структура, которая либо хранит int, либо "пусто". Подробнее — Nullable-типы в C#.
Boxing дорогой (аллокация + копирование). В горячих циклах избегайте присваивания int в object и старых необобщённых коллекций ArrayList. Детали размещения в памяти — Стек, куча и boxing.
Число
Число (int, double, decimal).
Значимые типы. decimal используется для финансовых вычислений (более точный, чем double).
Простой пример:
int age = 30;
Разбор:
ageобъявляется какint, то есть 32-битное целое число со знаком.- Литерал
30напрямую записывается в переменную при инициализации. - Такой тип подходит для счётчиков, индексов и небольших числовых диапазонов.
- Пример минимален, но подчёркивает базовый шаблон объявления: тип + имя + значение.
Сложный пример:
decimal taxAmount = subtotal * (region switch {
"EU" => 0.2m,
"US" => stateTaxRates[state],
"ASIA" => 0.15m,
_ => 0.0m
});
Разбор:
- Используется выражение
switch, которое возвращает значение в зависимости отregion. - Суффикс
mу литералов (0.2m) указывает типdecimal, важный для точных финансовых расчётов. - Ветка
"US"берёт ставку из словаряstateTaxRatesпо ключуstate. _ => 0.0m— ветка по умолчанию, если регион не распознан.- Итоговая ставка умножается на
subtotal, и результат сохраняется вtaxAmount.
Использование выражения switch для определения налоговой ставки в зависимости от региона. Результат умножается на сумму. Тип decimal гарантирует точность при финансовых операциях.
Булево
Булево (bool).
Примитивный тип, аналогичный Java.
Простой пример:
bool isActive = true;
Разбор:
boolхранит только два значения:trueилиfalse.- Переменная
isActiveобычно используется как флаг состояния сущности. - Инициализация
trueзадаёт начальное состояние "активен". - Такой тип часто участвует в условиях
if, фильтрации и валидации.
Сложный пример:
bool isValid = !string.IsNullOrEmpty(email) &&
email.Contains("@") &&
allowedDomains.Any(domain => email.EndsWith(domain)) &&
!blacklistedEmails.Contains(email);
Разбор:
- Выражение строит цепочку логических проверок через
&&; итогtrueтолько если все условия выполнены. !string.IsNullOrEmpty(email)защищает отnullи пустой строки.email.Contains("@")проверяет базовый признак формата e-mail.Any(domain => email.EndsWith(domain))ищет совпадение хотя бы с одним разрешённым доменом.!blacklistedEmails.Contains(email)исключает адреса из чёрного списка.
Проверка валидности email включает несколько условий — не null/empty, наличие символа @, соответствие домену и отсутствие в чёрном списке. Используется LINQ (Any, Contains).
Ссылочные типы
Ссылочные типы (Reference Types) содержат ссылку (адрес) на область памяти в куче, где находятся реальные данные.
Примеры:
string name = "John";
object obj = 42;
List<int> numbers = new List<int>();
Разбор:
string nameхранит ссылку на строковый объект в куче.object obj = 42;приводит значимый типintкobject(boxing).new List<int>()создаёт экземпляр обобщённой коллекции целых чисел.- Пример показывает, что ссылочные типы могут ссылаться на разные виды объектов.
Категории ссылочных типов:
- классы (class):
class Person {
public string Name { get; set; }
}
- интерфейсы (interface):
interface ILogger {
void Log(string message);
}
- делегаты (delegate):
delegate void Notify(string message);
- массивы (array):
int[] numbers = new int[] {1, 2, 3};
- тип object (базовый тип для всех типов в .NET);
- тип string (строка), неизменяемая (immutable) и имеет особое поведение.
При присваивании одного экземпляра ссылочного типа другому, копируется ссылка, а не сам объект:
Person p1 = new Person { Name = "Alice" };
Person p2 = p1; // p2 указывает на тот же объект
p1.Name = "Bob";
Console.WriteLine(p2.Name); // Выведет "Bob"
Особенности ссылочных типов:
- требуют управления памятью через Garbage Collector (GC);
- могут быть null;
- поддерживают наследование и полиморфизм;
- могут иметь сложную структуру и включать другие типы.
Строка
Строка (string).
Неизменяемый ссылочный тип. Богатый набор методов и поддержка интерполяции. Простой пример:
string name = "Анна";
Сложный пример:
string report = $@"
Отчёт за {DateTime.Now:MMMM yyyy}
-----------------------------------
Пользователь: {user.Name?.Trim() ?? "Не указан"}
Роль: {(user.Role.HasValue ? Enum.GetName(typeof(Role), user.Role) : "N/A")}
Баланс: {user.Balance:C}
Статус: {(user.IsActive ? "Активен" : "Заблокирован")}
".Trim();
Интерполяция с условными выражениями, безопасным доступом к свойствам (?.), форматированием валюты (:C) и именем перечисления. Результат — многострочный отчёт.
Play ITЗагрузка интерактивного демо…
Объект
Объект (class, record). Ссылочные типы. record — неизменяемые объекты (с C# 9).
Простой пример:
var person = new { Name = "Иван", Age = 28 };
Сложный пример:
var employees = dbContext.Employees
.Where(e => e.DepartmentId == deptId && e.Salary > minSalary)
.Select(e => new EmployeeSummary(
e.Id,
$"{e.FirstName} {e.LastName}".Trim(),
e.Position,
e.Salary * (1 + bonusPercentage)
))
.ToList();
LINQ-запрос к базе данных, фильтрация и проекция в DTO-объект с вычислением бонуса. Демонстрирует интеграцию с ORM (например, Entity Framework).
Play ITЗагрузка интерактивного демо…
Массив
Массив (array).
Фиксированная коллекция одного типа.
Простой пример:
int[] numbers = { 1, 2, 3 };
Сложный пример:
int[,] distanceMatrix = new int[cities.Length, cities.Length];
for (int i = 0; i < cities.Length; i++)
{
for (int j = 0; j < cities.Length; j++)
{
distanceMatrix[i, j] = (int)CalculateDistance(cities[i], cities[j]);
}
}
Двумерный массив расстояний между городами. Заполняется через вложенный цикл и вызов функции. Типичный случай для задач маршрутизации.
Делегат / функция
Делегат / Функция (Func, Action).
Типы-обёртки для функций. Func<T>, TResult>, Action<T>.
Простой пример:
Func<string, string> greet = name => $"Привет, {name}!";
Сложный пример:
Func<User, bool> isPremium = u =>
u.SubscriptionLevel == Subscription.Premium &&
u.LastLogin > DateTime.Now.AddDays(-30) &&
u.TotalPurchases.Sum(p => p.Amount) > 1000;
var premiumUsers = allUsers.Where(isPremium).ToList();
Делегат Func<User, bool> инкапсулирует правило определения премиум-пользователя. Затем используется в LINQ-запросе. Позволяет переиспользовать логику проверки.
Когда какой тип выбрать
- простые частые данные — встроенный значимый тип (
int,bool,DateTime); - логически связанная группа полей —
structилиrecord struct; - объекты с поведением, наследованием и коллекциями —
class; - фиксированный набор состояний —
enum; - компактность и предсказуемость копирования —
readonly struct.
Виды массивов
Массив – упорядоченная коллекция элементов одного типа.
Одномерный массив:
int[] numbers = { 1, 2, 3 };
Многомерный массив:
int[,] matrix = {
{ 1, 2 },
{ 3, 4 }
};
Jagged-массив (массив массивов):
int[][] rows = new int[][] {
new int[] { 1, 2 },
new int[] { 3, 4, 5 }
};