Когда оператор using блокирует свой аргумент, когда это структура? - PullRequest
5 голосов
/ 25 августа 2009

У меня есть несколько вопросов по поводу следующего кода:

using System;

namespace ConsoleApplication2
{
    public struct Disposable : IDisposable
    {
        public void Dispose() { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (Test()) { }
        }

        static Disposable Test()
        {
            return new Disposable();
        }
    }
}

Мои вопросы:

  • Будет ли оператор using, работающий со структурой Disposable, возвращаемый из Test(), содержать структуру или нет?
  • Как я могу найти ответ сам?

Чтобы попытаться выяснить это самостоятельно, я проверил IL, созданный с помощью вышеуказанного кода, и вот IL для метода Main(...):

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] valuetype ConsoleApplication2.Disposable CS$3$0000)
    L_0000: call valuetype ConsoleApplication2.Disposable ConsoleApplication2.Program::Test()
    L_0005: stloc.0 
    L_0006: leave.s L_0016
    L_0008: ldloca.s CS$3$0000
    L_000a: constrained ConsoleApplication2.Disposable
    L_0010: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0015: endfinally 
    L_0016: ret 
    .try L_0006 to L_0008 finally handler L_0008 to L_0016
}

Я подозреваю, что вызов виртуального метода там, на L_0010 введет операцию упаковки, но фактической инструкции box здесь нет.

Причина, по которой я спрашиваю, состоит в том, что некоторое время назад, возможно, 1-2 года, я увидел в сети «оптимизацию» заявления об использовании, которое кто-то прокомментировал. Был случай, когда оператор using был использован в качестве синтаксиса для кратковременной блокировки объекта, когда была получена блокировка в методе, и была возвращена структура, которая при удалении снимала блокировку, код, подобный этому :

using (LockTheObject())
{
    // use the object
}

и комментарий состоял в том, что, изменив тип возвращаемого значения метода LockTheObject с IDisposable на фактическую используемую структуру, можно было избежать упаковки.

Но мне интересно, правда ли это или все еще правда.

Кто-нибудь может указать мне правильное направление? Если, чтобы увидеть работу блока, мне нужно будет проверить код сборки во время выполнения, пожалуйста, покажите мне пример того, что искать, я хорошо разбираюсь в коде сборки, так что это не проблема, но ничего не выпрыгнуло на меня, когда я смотрел на это тоже.

Ответы [ 3 ]

7 голосов
/ 26 августа 2009

Похоже, что любой тип значения, помещенный в оператор using, не будет упакован. Похоже, это оптимизация на C #, так как бокс не используется, только когда тип значения, который реализует IDisposable, находится в операторе using, а не в каком-либо другом контексте.

Для получения дополнительной информации см. Оператор использования и типы одноразовых значений :

Некоторое время назад Ян Гриффитс писал о улучшение его класса TimedLock в котором он изменил его из класса в структура. Это изменение привело к тип значения, который реализует IDisposable. У меня был ноющий вопрос в глубине души в то время о котором я быстро забыл. Вопрос не в том, что наберите get boxed при вызове Dispose?

А также О, нет! Снова тайм-лок! :

Джон Сэндс указывает на изъян в код, который я показал в недавнем блоге для используя таймауты на замках без отказаться от большей части удобства Ключевое слово C # lock.

5 голосов
/ 26 августа 2009

Это дубликат Если моя структура реализует IDisposable, будет ли он помещен в коробку при использовании в операторе using?

ОБНОВЛЕНИЕ: Этот вопрос был темой моего блога в марте 2011 года . Спасибо за отличный вопрос!

Эндрю Харе ответ правильный; Я просто хотел добавить интересную дополнительную заметку. Оптимизация, которую мы испускаем - использование ограниченной callvirt для пропуска бокса, когда это возможно, - фактически строго говоря, является нарушением спецификации C #. В спецификации говорится, что блок finally, который мы генерируем для ресурса типа значения:

     finally 
     {
         ((IDisposable)resource).Dispose();
     }

, который явно является бокс-преобразованием для типа значения. Можно построить надуманные сценарии, в которых видна нехватка бокса в реализации.

(Спасибо Владимиру Решетникову за то, что он указал мне на это нарушение спецификации.)

3 голосов
/ 26 августа 2009

Методы экземпляра для типа значения принимают параметр this в качестве первого аргумента, аналогично тому, как это делают методы экземпляра в ссылочных типах. Однако параметром в этом случае является управляемый указатель на данные объекта, а не ссылка на упакованный объект. Вы можете найти это в памяти так:

Unboxed object:
-----------------------------------------
|              DATA                     |
-----------------------------------------
 ^ managed pointer to struct

Boxed object:
------------------------------------------------------------
| GC/Object header |              [Boxed] DATA             |
------------------------------------------------------------
                    ^ The 'unbox' opcode gives a managed pointer to the boxed data
 ^ A *reference* to any instance of a reference type or boxed object, points here

DATA одинаково в обоих этих случаях¹.

Методы экземпляра для типа значения ожидают управляемого указателя на данные , в частности, , поэтому упаковка объектов не требуется. Как вы видите выше, код операции constrained используется перед вызовом. Он сообщает среде выполнения, что следующая инструкция callvirt получает управляемый указатель на структуру ConsoleApplication2.Disposable вместо ссылки на объект, которую она обычно получает. При этом JIT может разрешить запечатанную перегрузку Dispose(), реализованную структурой, и вызвать ее напрямую, не упаковывая объект. Без префикса constrained объект, переданный инструкции callvirt, должен был бы быть ссылкой на объект, поскольку стандартная процедура динамического разрешения виртуального вызова основана на том факте, что заголовок GC / Object равен всегда в ожидаемом месте - и да, это вызовет бокс для типов значений.

¹ Пока что будем игнорировать Nullable<T>.

...