Как я понимаю, дженерики - это элегантное решение для решения проблем с дополнительными процедурами упаковки / распаковки, которые происходят в общих коллекциях, таких как List<T>
.
Устранение бокса было задним числомсценарий для дженериков, да.Но, как указывает Дэмиен в комментарии, более общей функцией было включение более лаконичного, более безопасного для типов кода.
, если я хочу передать экземпляр значения, который реализует интерфейс универсального метода,будет ли выполняться бокс?
Иногда да.Но поскольку бокс стоит дорого, CLR ищет способы его избежать.
Я думал, что add(new MyInt(1), new MyInt(2))
будет использовать операции бокса, потому что метод add generic использует интерфейс INum<a>
Я понимаю, почему вы сделали этот вывод, но это неправильно.То, как тело метода, который вы назвали , использует , информация не имеет значения.Вопрос в том, что такое сигнатура метода, который вы вызываете?Вывод типа C # определяет, что вы звоните add<MyInt>
, и, следовательно, подпись эквивалентна звонку:
public static MyInt add(MyInt lhs, MyInt rhs)
Теперь вы справедливо указываете на наличие ограничения.Компилятор C # проверяет соблюдение ограничения, которым оно является. Это не меняет соглашение о вызовах метода .Метод занимает два MyInt
с, и вы передали ему два MyInt
с, и они являются типами значений, поэтому они передаются по значению.
Кажется, что newobj не создаетэкземпляр значения в куче, для значений он создает их в стеке.
Убедитесь, что это понятно: он создает их в стеке абстрактной оценки программы IL .Превращает ли джиттер этот код в код, который помещает значения в реальный стек текущего потока, - это деталь реализации джиттера.Например, он может поместить их в регистры или в структуру данных, которая имеет логические свойства стека, но на самом деле хранится в куче, или как угодно.
add
делаеттакже не содержит инструкций на коробке
Да, вы их просто не видите.Он содержит ограниченную callvirt, которая является условным блоком.
ограниченная callvirt имеет семантику:
должна быть ссылка на получателя в стеке.Там есть: ldarga
помещает адрес получателя в стек.Если получатель является ссылочным типом, адрес переменной, содержащей ссылку, будет в стеке.Если это тип значения, то адрес переменной, которая содержит тип значения, будет в стеке.(Опять же, это стек виртуальной машины, о котором мы рассуждаем здесь.)
аргументы должны быть в стеке.Они есть;аргументом INum<MyInt>.add
является MyInt
, и опять же, который передается по значению, и значение находится в стеке из ldarg
.
, если получательссылочный тип, мы тогда разыменовываем двойную ссылку, которую мы только что создали, чтобы получить ссылку, и виртуальный вызов происходит нормально.(Конечно, джиттер может оптимизировать эту двойную ссылку! Помните, что все эти семантики, которые я описываю, относятся к виртуальной машине программы IL, а не к реальной машине, на которой вы ее запускаете!)
если получатель является типом значения и тип значения реализует метод, который вы вызываете, то метод типа значения вызывается как обычно: то есть без упаковки значения. Это ваш случай в , поэтому мы избегаем бокса.
, если получатель является типом значения, который не реализует вызываемый вами метод,затем тип значения упаковывается в рамку, и метод вызывается со ссылкой на поле в качестве получателя. Упражнение для читателя: Создайте программу, подходящую для этого случая.
Что не так с моими предположениями?
Вы предполагали, что вызовы методов для типов значений через интерфейсы должны блокировать получателя, но это не так.
Могут ли дженерики вызывать виртуальные методы значений без упаковки?
Да.