Struct или Class в C#: когда что выбрать новичку?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Struct или Class в C#: вечный спор новичков и профи

Вы когда-нибудь задумывались, почему в C# есть два очень похожих способа создать свой тип данных — class и struct? И почему на Stack Overflow вопрос «When should I use a struct rather than a class in C#?» собирает тысячи просмотров и сотни ответов? Дело в том, что за кажущейся простотой скрывается глубокий нюанс, который может как ускорить вашу программу, так и превратить её в рассадник багов.

В этой статье мы разберём, чем struct отличается от class, когда стоит использовать struct, а когда лучше забыть о её существовании. Без заумных терминов, с живыми примерами и практическими советами. Поехали!

Главное отличие: стек или куча?

Самый важный момент, который нужно понять — типы значений (value types) и ссылочные типы (reference types).

  • Class — ссылочный тип. Объект хранится в управляемой куче (heap), а переменная содержит только ссылку (адрес) на этот объект.
  • Struct — тип значения (value type). Переменная хранит сами данные непосредственно в стеке (stack) или внутри другого объекта.

Звучит абстрактно? Давайте на примере. Представьте, что данные — это книга. Class — это название книги и номер полки, где она лежит в библиотеке. Struct — это сама книга, которую вы носите в кармане. Когда вы передаёте class методу, вы передаёте номер полки. Когда передаёте struct — вы отдаёте копию книги.

Пример кода: class vs struct

public class PointClass
{
    public int X { get; set; }
    public int Y { get; set; }

    public PointClass(int x, int y)
    {
        X = x;
        Y = y;
    }
}

public struct PointStruct
{
    public int X { get; set; }
    public int Y { get; set; }

    public PointStruct(int x, int y)
    {
        X = x;
        Y = y;
    }
}

// Использование
var pointClass = new PointClass(10, 20);
var pointStruct = new PointStruct(10, 20);

// Передача в метод
void ModifyPointClass(PointClass p) { p.X = 100; }
void ModifyPointStruct(PointStruct p) { p.X = 100; }

ModifyPointClass(pointClass);   // pointClass.X станет 100
ModifyPointStruct(pointStruct); // pointStruct.X останется 10 — копия!

Видите? Struct копируется при передаче. Это и есть ключевое отличие, которое определяет, когда её стоит использовать.

Когда struct — ваш друг: 3 железобетонных случая

Майкрософт рекомендует использовать struct, если ваш тип данных удовлетворяет ВСЕМ следующим условиям:

1. Данные — маленькие и простые

Struct идеально подходит для небольших наборов данных (рекомендуется не более 16-24 байт). Например, точка, цвет, вектор, комплексное число, координаты.

public struct RgbColor
{
    public byte R { get; }
    public byte G { get; }
    public byte B { get; }

    public RgbColor(byte r, byte g, byte b)
    {
        R = r;
        G = g;
        B = b;
    }
}

Почему? Потому что маленькие struct копируются быстро, а хранение в стеке снижает нагрузку на сборщик мусора (GC).

2. Объекты живут недолго и не меняются

Если ваши данные — это неизменяемые (immutable) значения, struct — отличный выбор. Например, координаты на карте: вы не меняете точку, вы создаёте новую.

public struct GeoLocation
{
    public double Latitude { get; }
    public double Longitude { get; }

    public GeoLocation(double lat, double lon)
    {
        Latitude = lat;
        Longitude = lon;
    }

    public GeoLocation MoveNorth(double meters)
    {
        // Возвращаем новый экземпляр, не меняем старый
        return new GeoLocation(Latitude + meters / 111111.0, Longitude);
    }
}

3. Производительность критична

Если вы создаёте миллионы маленьких объектов (например, в играх, симуляциях, научных расчётах), struct может дать прирост скорости до 30-50% за счёт отсутствия выделения памяти в куче.

// Массив из 1 000 000 точек — struct будет быстрее
PointStruct[] points = new PointStruct[1_000_000];
// vs
PointClass[] pointsClass = new PointClass[1_000_000];
// Для class нужно ещё создать каждый объект!

Когда struct — злейший враг: 4 красных флага

А теперь давайте посмотрим, когда от struct лучше держаться подальше.

1. Данные большие (более 24 байт)

Каждый раз при передаче в метод, возврате из метода или присваивании — полная копия всех полей. Представьте struct с 10 полями типа double (80 байт) — копирование такого объёма данных начнёт тормозить.

2. Вы планируете часто изменять объект

Struct по умолчанию должна быть неизменяемой. Если вы напишете:

public struct MutablePoint
{
    public int X; // Плохо! Публичное поле
    public int Y;
}

И будете менять X напрямую, вы рискуете получить неожиданное поведение, потому что работаете с копией. Мутабельные struct — источник багов.

3. Наследование и полиморфизм

Struct не может наследовать от другого struct или class (кроме System.ValueType). Никаких виртуальных методов, абстрактных классов. Если вам нужно наследование — только class.

4. Частые упаковки (boxing/unboxing)

Когда вы передаёте struct в метод, ожидающий object или interface, происходит упаковка — данные копируются в кучу. Это жрёт память и процессор. Если ваш код активно использует коллекции типа ArrayList (старый стиль) или LINQ с интерфейсами — class будет безопаснее.

Практический чек-лист: как принять решение

Когда сомневаетесь, задайте себе 5 вопросов:

  1. Размер данных меньше 16 байт? (посчитайте: int = 4, double = 8, bool = 1)
  2. Данные неизменяемые? (все поля readonly, нет set-методов)
  3. Не нужно наследование?
  4. Объекты создаются в большом количестве и живут недолго?
  5. Не будете часто упаковывать (boxing)?

Если на все вопросы ответ «ДА» — смело берите struct. Если хоть один «НЕТ» — выбирайте class.

Реальный пример из жизни: игровой движок

Представьте, вы пишете

Блоги

Рекомендаций по книгам