Pull to refresh

Comments 4

у вас в проде нашёлся самый неожиданный боксинг

Самый неожиданный боксинг был по поведению для nullable-типов в современном C#. Так приведение nullable-типа к object при боксинге меняется тип. Например, при приведении Int32? varialble к object боксинг произойдёт только в том случае, если variable был не null. Если был null, то боксинга не будет, просто переменной типа object присвоится null и сведения о изначальном типе потеряются и потом при анбоксинге можно будет привести это значение хоть к Boolean?.

Как только struct присваивается переменной интерфейсного типа, он боксится — ведь интерфейс ссылочный. Вызов s.Area() идёт уже по упакованной копии. Если вы держите Circle в локальной переменной конкретного типа и вызываете Area() напрямую — боксинга нет. Грабли вылезают, когда struct “прячут” за интерфейс ради полиморфизма.

Лечение: generic-ограничение where T : struct, IShape вместо переменной интерфейса — JIT специализирует код по value-типу и вызывает метод без упаковки.

Ссылочность вообще не причем. Структуры прекрасно передаются по ссылке без всяких аллокаций. Проблема в том что каждая передаваемая переменная должна иметь фиксированный заранее известный размер. Если переменная интерфейсного типа то размер фактически передаваемых данных может быть любым до 0 и далее. Не может быть переменной или параметра, размер которого никто не знает при исполнении. В отличии от ссылки размер которой всегда фиксированный. Боксинг это способ впихнуть невпихуемое.

Когда мы пишем ограничение where T : struct, IShape то это совсем другая история, так как это скрывает вызов отдельной предварительно сгенерированной функции для каждого используемого типа. У каждой функции будет свой параметр фиксированного размера.

Боксинг, который уже НЕ боксит: чему научился рантайм

Generic-методы со struct-аргументом не боксят сам аргумент. EqualityComparer<T>.DefaultList<T>Dictionary<TKey,TValue> специализируются по value-типу: JIT генерирует отдельный нативный код на каждый struct-аргумент, и упаковки value→object там нет (при условии корректного IEquatable<T> — см. пункт 3).

Что значит уже? В C# изначально мономорфизация параметров типов что значит что любой вариант типа с параметром мономорфизируеться в отдельный тип, там нечему бокситься и незачем. Другими словами, не было никакого List<int> всегда был List_int.

Боксинг это способ впихнуть невпихуемое.

Любопытная интерпретация, но, боюсь, неверная.

У вас, всё-таки, ошибка в методе лечения.
>Лечение: generic-ограничение where T : struct, IShape вместо переменной интерфейса — JIT специализирует код по value-типу и вызывает метод без упаковки.

struct в ограничении параметра интерфейса совсем необязательно указывать. Важно сделать аргументом метода не интерфейс, а generic-тип, на который наложено ограничение имплементировать нужный интерфейс. Тогда, как писал ещё Джеффри Рихтера, C# для reference-типов создаст одну общую имплементацию этого метода, а для каждого структурного типа сделает отдельную имплементацию. Эта отдельная имплементация будет знать конкретный тип параметра и не будет боксинга.

Вот ваш немного переделанный пример на sharplab. Ограничения struct нет, а боксинг лечится.
https://sharplab.io/#gist:a9fa36483d3aa22fb79b12b3704efac6

using System;
public interface IShape { double Area(); }

public struct Circle : IShape
{
    public double R;
    public double Area() => Math.PI * R * R;
}

public class C {
    public double GetDoubleAreaWithBoxing() {
        IShape s = new Circle { R = 2 };
        return s.Area();
    }

    public double GetDoubleAreaWithoutBoxing() {
        Circle circle = new Circle { R = 2 };
        return GetShapeDoubleArea(circle);
    }
    
    public double GetShapeDoubleArea<T>(T shape) where T: IShape {
        var area = shape.Area();
        return area * 2;
    }
}



Sign up to leave a comment.

Articles