Будет ли происходить боксинг (boxing) при сравнении двух структур (value types) в C#?

«Будет ли происходить боксинг (boxing) при сравнении двух структур (value types) в C#?» — вопрос из категории C# Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Краткий ответ: Зависит от способа сравнения. При использовании переопределенного метода Equals() или оператора == для структуры боксинга не происходит. Боксинг может случиться при приведении структуры к типу интерфейса или при вызове унаследованного от object метода.

Детали и примеры:

  1. Сравнение БЕЗ боксинга (рекомендуемый способ):

    public readonly struct Point : IEquatable<Point>
    {
        public int X { get; }
        public int Y { get; }
        // Реализация IEquatable<T> избегает боксинга
        public bool Equals(Point other) => X == other.X && Y == other.Y;
        // Переопределение Equals(object) требует боксинга для 'obj', но это редко используемый путь
        public override bool Equals(object obj) => obj is Point other && Equals(other);
        // Перегрузка оператора ==
        public static bool operator ==(Point left, Point right) => left.Equals(right);
    }
    
    Point p1 = new Point(1, 2);
    Point p2 = new Point(1, 2);
    bool isEqual1 = p1.Equals(p2);        // НЕТ боксинга (вызов IEquatable<Point>.Equals)
    bool isEqual2 = p1 == p2;             // НЕТ боксинга (вызов перегруженного оператора)
  2. Сравнение, которое ВЫЗЫВАЕТ боксинг:

    Point p1 = new Point(1, 2);
    Point p2 = new Point(1, 2);
    
    // БОКСИНГ: Приведение к необобщенному интерфейсу
    IEquatable ie = p1; // Боксинг для p1!
    bool isEqual3 = p1.Equals((object)p2); // Боксинг для p2 (вызов Equals(object))
    
    // БОКСИНГ: Использование в коллекциях типа object (например, ArrayList)
    ArrayList list = new ArrayList();
    list.Add(p1); // Боксинг при добавлении!

Почему это важно? Боксинг создает новый объект в куче (heap), что приводит к дополнительным затратам на выделение памяти и последующую сборку мусора (GC). В высокопроизводительном коде или в циклах это может стать узким местом. Всегда реализуйте для структур интерфейс IEquatable<T> и используйте обобщенные коллекции (List<Point> вместо ArrayList).