Интересные моменты в C# (boxing unboxing)

    В этой статье мы коротко пройдемся по малоизвестным особенностям boxing/unboxing.

    Предыдущая статья о foreach
    Предыдущая статья об Array

    Типичный вопрос на собеседовании об упаковке и распаковке выглядит следующим образом — «Что будет при запуске данного кода, и если он не будет работать то как его исправить?».

    Тестовый код:
    object box = (int)42;
    long unbox = (long)box;
    

    Ответ может быть следующий — «При распаковке первый оператор является не приведением типов а распаковкой типа, соответственно он должен соответствовать типу значения находящегося в запакованном виде.».

    Правильный ответ:
    object box = (int)42;
    long unbox = (long)(int)box;
    

    Обычно это считается правильным ответом, но это не совсем так…

    Unboxing и Enum


    Представьте себе удивление человека, когда вы ему напишете другой правильный вариант.

    Второй правильный ответ:
    public enum EnumType { None }
    ...
    object box = (int)42;
    long unbox = (long)(EnumType)box;
    

    Напомню, enum не является фундаментальным типом и не наследует его, он является структурой содержащей фундаментальный тип (базовый). Это говорит о том что в .NET есть явная поддержка такой распаковки. Так же легко проверить что распаковка не использует операторы явного и неявного преобразования и интерфейс IConvertible и свой тип не получится развернуть из чужого типа.
    При распаковке enum'а используется его базовый тип и следующая распаковка не будет работать.

    Неправильный вариант:
    public enum EnumType : short { None }
    ...
    object box = (int)42;
    long unbox = (long)(EnumType)box;
    

    Распаковка для enum'ов ослаблена предельно.

    Распаковываем int из enum'а:
    public enum EnumType { None }
    ...
    object box = EnumType.None;
    long unbox = (long)(int)box;
    

    Распаковываем один enum из другого:
    public enum EnumType { None }
    public enum EnumType2 { None }
    ...
    object box = EnumType.None;
    long unbox = (long)(EnumType2)box;
    

    Unboxing и Nullable


    Распаковка поддерживает и Nullable типы, что кажется более логичным.

    Распаковка Nullable типа из обычного:
    object box = (int)42;
    long unbox = (long)(int?)box;
    

    Распаковка обычного типа из Nullable:
    object box = (int?)42;
    long unbox = (long)(int)box;
    

    Напомню что Nullable это структура с одним обобщенным типом значения и предназначена для хранения данных и флага присутствия данных. Это говорит о том что в C# есть явная поддержка распаковки Nullable типов. В новых версиях C# для этой структуры появился alias "?".

    Nullable:
    public struct Nullable<T> where T : struct
    {
        public bool HasValue { get; }
        public T Value { get; }
    }
    

    Равнозначные записи:
    Nullable<int> value;
    int? value;
    

    Всем спасибо за внимание!
    Поделиться публикацией

    Похожие публикации

    Комментарии 10
      +6
      Еще по поводу распаковки Nullable. Ключевое слово as работает с такими типами, значение null получается при любой неудачной распаковке:
      object box = 42L;
      int? unbox = box as int?; // null
      

      Это довольно удобно при работе с ADO.NET, поскольку значение DBNull автоматически превращается в обычный null во время распаковки.
        +3
        Типичный вопрос на собеседовании об упаковке и распаковке выглядит следующим образом — «Что будет при запуске данного кода, и если он не будет работать то как его исправить?».

        Тестовый код:
        object box = (int)42; long unbox = (long)box;

        Вы так и не ответили на свой вопрос: что будет при запуске кода? Если будет ошибка компиляции с очевидным сообщением, то всё остальное запоминать не нужно, и задвать такие вопросы на собеседовани — трата времени всех участвующих.

        То есть информация, конечно, интересная и полезная, но не то чтобы очень важная.
          +1
          Будет исключение времени выполнения.
            +3
            А конкретнее InvalidCastException.
              0
              Тогда OK, но это стоит добавить в статью.
          +6
          Подобное поведение при распаковке не является частью стандарта языка. Т.е., да, оно работает, но это незадокументированное расширение, и полагаться на него не стоит. Аналогично с приведением массивов, когда int[] можно успешно скастить к uint[] через object.

          А синтаксис T? для Nullable<T> появился в той же версии языка, что и сам Nullable — C# 2.0. И, кстати говоря, вот эти два варианта дают один и тот же результат:
          object x = (int)123;
          object x = (int?)123;
          

          Т.е. пакуется не Nullable, а его значение. Если спросить у полученного object его тип через GetType(), то вы получите int, а не Nullable. Если значения нет — после запаковки получается null. Поэтому такой вещи, как «распаковка из Nullable», просто не существует.

          Вообще, про такие вещи лучше все-таки читать в спецификации, а не пытаться реверс-инжинирить их на кусках кода.
            –2
            Вообще, про такие вещи лучше все-таки читать в спецификации, а не пытаться реверс-инжинирить их на кусках кода.

            Вы же сами сказали что его нет в документации.
              +2
              Про Nullable как раз все есть.

              А про то, чего нет, потому и лучше читать документацию, что вы потом в коде не будете закладываться на вещи, которые сегодня есть, а завтра их нет :)

              Нет, понятно, что иногда бывает интересно ковыряться в такого рода деталях, как и в различных вариантах undefined behavior в C++. Но это надо подавать соответствующим образом — не «C# это поддерживает», а «посмотрите, какой забавный глюк».
                –1
                Я не собираюсь это использовать. Статья называется «интересные моменты» а не «правильное использование». С Уважением.
            +1
            > Представьте себе удивление человека, когда вы ему напишете другой правильный вариант.

            Давайте назовём это «работающий вариант», а не «правильный»? А то ведь начнут писать так.

            Для ещё худшей читаемости кода надо было назвать enum, скажем, StringType.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое