Почему null оператор ToString() возвращает пустую строку?

Мне просто интересно, в чем разница между двумя следующими утверждениями:

  • Вызывает исключение NullReferenceException - это нормально.

    object test1 = default(int?);
    object result = test1.ToString();
    
  • Возвращает пустую строку "", почему?

    object test2 = default(int?).ToString();
    
  • Это то же самое, что и 2.

    int? test3 = null;
    if (test3.ToString() == string.Empty) //returns true
    {
        Console.WriteLine("int? = String.Empty, isn't it strange?").
    }
    
  • И просто для удовольствия - я могу доказать, что bool может быть равно int value (hmmm, как? bool может быть только ложным, или true, и int никогда не может быть таким).

    if (default(int?).ToString() == default(bool?).ToString()) //returns true because both are empty strings
    {
        Console.WriteLine("int = bool");
    }
    

Примечание: default (int?) возвращает null.

Ответы

Ответ 1

An int? является Nullable<int>. Nullable - это struct, что делает его типом значения, а не ссылочным типом. test3 на самом деле не установлен на null. Присвоение ему null на самом деле приводит к созданию новой структуры, которая просто имеет поле HasValue этой структуры для false вместо true. (Для такого неявного преобразования требуется специальная поддержка компилятора, поэтому вы не могли бы использовать свой собственный тип MyNullable<T>.)

Поскольку объект с нулевым значением имеет фактическое значение, он может вызывать ToString и предоставлять содержательное значение, в данном случае пустую строку.

Когда вы установите это значение с нулевым значением, поместив его в переменную object, это приведет к тому, что в этой переменной будет сохранено фактическое значение null, поэтому вы получаете NRE при вызове метода на нем.

Когда тип с нулевым значением вставляется в бокс, он не вводит тип значения NULL; вместо этого, если он не имеет значения, он присваивает объекту null, если он имеет значение, то базовое значение разворачивается и затем помещается в бокс. (Для этого требуется специальная поддержка из среды выполнения, что является еще одной причиной, по которой вы не можете создать свой собственный тип MyNullable<T>.)

Ответ 2

В C используется токен . для доступа к элементу левого операнда и -> для доступа к элементу объекта, в который левый операнд содержит указатель (ссылку). Нулевая ссылка возникает, если левый операнд -> имеет значение null. В С# значение . имеет прежнее значение при применении к типам значений, а последнее - при применении к типам классов; используя . в С# в переменной типа класса, которая равна null, вызывается NullReferenceException.

Тип .NET Nullable<T> представляет собой структуру с двумя полями, которая содержит логическое поле, которое указывает, имеет ли значение значение (не нуль), а поле типа значения T, чтобы сказать, что это за значение (если не ноль). Хотя компилятор позволит назначить Nullable<T> null или сравнить его с нулевым значением, null в первом случае на самом деле является просто сокращением экземпляра по умолчанию (где флаг "имеет значение" является ложным, а value имеет значение по умолчанию для типа T), а сравнение с null - это просто сокращение для проверки свойства HasValue (которое, в свою очередь, смотрит в поле Boolean). Вызов ToString напр. a Nullable<int>, который является "нулевым", не отличается семантически от вызова его на любую другую структуру, которая содержит Boolean и Int32, которые имеют значения False и 0 соответственно. Существует несколько способов, с помощью которых компиляторы обрабатывают типы с нулевым значением "специально", но значение Nullable<int> по умолчанию содержит [False, 0], а не нулевую ссылку.

Обратите внимание, что первый пример выбрасывается из-за одного из "специальных" правил, которые применяются к типам с нулевым значением. Передача значения типа Object или другого ссылочного типа обычно дает указание среде выполнения создать объект, который будет вести себя как объект класса с полями и членами, соответствующими строкам и возвращать ссылку на этот новый объект - процесс, известный как "заниматься боксом". Поскольку разработчики типов с нулевым значением хотели разрешить коду говорить if (NullableThing == null), а не (IMHO sematic clearer) if (!NullableThing.HasValue), среда выполнения была реализована так, чтобы бокс с нулевым значением, чье поле HasValue было ложным, даст нулевую ссылку, а не ссылка на экземпляр Nullable<T> по умолчанию. Хотя я думаю, что есть случаи, когда было бы целесообразно использовать некоторые типы значений, используя разные методы box и unbox, типы с нулевым значением являются единственными, которые могут использовать что-либо, кроме обычного бокса.