Почему невозможно использовать оператор is для различения между bool и Nullable <bool>? - PullRequest
0 голосов
/ 10 января 2019

Я сталкивался с этим, и мне любопытно, почему невозможно использовать оператор is для различения между bool и Nullable<bool>? Пример;

void Main()
{
    bool theBool = false;
    Nullable<bool> theNullableBoolThatsFalse = false;
    Nullable<bool> theNullableBoolThatsNull = null;

    void WhatIsIt(object value)
    {
        if(value is bool)
            Console.WriteLine("    It's a bool!");
        if(value is Nullable<bool>)
            Console.WriteLine("    It's a Nullable<bool>!");
        if(value is null)
            Console.WriteLine("    It's a null!");
    }

    Console.WriteLine("Considering theBool:");
    WhatIsIt(theBool);
    Console.WriteLine("Considering theNullableBoolThatsFalse:");
    WhatIsIt(theNullableBoolThatsFalse);
    Console.WriteLine("Considering theNullableBoolThatsNull:");
    WhatIsIt(theNullableBoolThatsNull);
}

Звонит Main() дает;

Considering theBool:
    It's a bool!
    It's a Nullable<bool>!
Considering theNullableBoolThatsFalse:
    It's a bool!
    It's a Nullable<bool>!
Considering theNullableBoolThatsNull:
    It's a null!

Я бы ожидал;

Considering theBool:
    It's a bool!
Considering theNullableBoolThatsFalse:
    It's a Nullable<bool>!
Considering theNullableBoolThatsNull:
    It's a null!

Почему оба bool и Nullable<bool> соответствуют друг другу?

Что я пробовал;

  • Я ознакомился с документами по Nullable, is, switch и сопоставлению с образцом.
  • Я думаю, это может быть связано с распаковкой значения, когда оно передается в метод?

Я думаю, что это может быть уникально для Nullable, потому что я не сталкиваюсь с такими же проблемами для других универсальных типов. Например;

void Main()
{
     bool theBool = false;
     List<bool> theListOfBool= new List<bool>();    

     void WhatIsIt(object value)
     {
         if(value is bool)
             Console.WriteLine("    It's a bool!");
         if(value is List<bool>)
             Console.WriteLine("    It's a List<bool>!");
     }

     Console.WriteLine("Considering theBool:");
     WhatIsIt(theBool);
     Console.WriteLine("Considering theListOfBool:");
     WhatIsIt(theListOfBool);
}

Придает;

Considering theBool:
    It's a bool!
Considering theListOfBool:
    It's a List<bool>

Я не пытаюсь решить проблему. Просто интересно, почему так работает.

Ответы до сих пор предполагают, что именно преобразования implicit и explicit вызывают такое поведение, но я не смог выполнить репликацию в следующем примере;

class A
{
    public static implicit operator A(B value) => new A();
    public static explicit operator B(A value) => new B();
}

class B
{
    public static implicit operator A(B value) => new A();
    public static explicit operator B(A value) => new B();
}

static void Main(string[] args)
{
    var a = new A();
    var b = new B();

    void WhatIsIt(object value)
    {
        if (value is A)
            Console.WriteLine("    It's a A!");
        if (value is B)
            Console.WriteLine("    It's a B!");
    }

    Console.WriteLine("Considering a;");
    WhatIsIt(a);
    Console.WriteLine("Considering b;");
    WhatIsIt(b);
}

Придает;

Considering a;
    It's a A!
Considering b;
    It's a B!

Документы для is говорят:

Он рассматривает только ссылочные преобразования, преобразования в блокировку и преобразования в распаковку; он не учитывает пользовательские преобразования или преобразования, определенные неявными и явными операторами типа. В следующем примере генерируются предупреждения, потому что результат преобразования известен во время компиляции. Обратите внимание, что это выражение для преобразований из int в long и double возвращают false, поскольку эти преобразования обрабатываются неявным оператором.

Решения, которые решает фреймворк, - ссылочные преобразования, преобразования в боксы и распаковки?

Ответы [ 4 ]

0 голосов
/ 10 января 2019

Причина, по которой bool и Nullable<bool> ведут себя одинаково при передаче в ваш метод, заключается в том, что всякий раз, когда вы добавляете Nullable<T>, он на самом деле не упаковывает значение, допускающее обнуляемость, а разворачивает значение обнуляемого значения и блоков, которые , Если обнуляемое значение равно нулю, то в итоге вы получите просто null, а не в штучной упаковке Nullable<T>, где HasValue равно false.

Если вы укажете ненулевое значение, оно просто пометит Value из Nullable<T>. Таким образом, с точки зрения WhatIsIt, первые два вызова буквально неразличимы , потому что точно такое же значение передается в .

Это оставляет вопрос о том, почему обе is проверки возвращают true, хотя то, что передается, в обоих случаях является логическим значением в штучной упаковке, а не Nullable<T>. На это отвечают спецификации языка C #, раздел 7.10.10:

Если T является обнуляемым типом, результат равен true, если D является базовым типом для T.

В этом случае учитывается E is T, а D определяется ранее как вычисленное значение E, где:

Если тип E является обнуляемым типом, D является базовым типом этого обнуляемого типа.

Это означает, что оператор is является , в частности , который определен как трактующий обнуляемые типы как эквивалентные их базовым типам, независимо от того, как вы смешиваете и сопоставляете фактическое проверяемое значение и тип, который вы используете. проверка с помощью значений Nullable и базового типа этого Nullable.

0 голосов
/ 10 января 2019

Значение false можно безопасно преобразовать в bool и bool?, поскольку между ними существует неявный оператор приведения.

null, с другой стороны, не может быть преобразовано в bool, поэтому null is bool возвращает false.

Оператор is не (и не может) заботиться о том, как вы объявили переменную - если вообще. Он просто указывает тип значения , предоставленного во время выполнения . Вы могли бы также написать это:

WhatIsIt(false)

Как вы ожидаете, что метод будет вести себя здесь? Он просто пытается преобразовать значение в оба типа - что может - и, таким образом, возвращает true для обоих типов.

Почему это не работает таким образом для других обобщенных типов просто потому, что не существует неявного преобразования между наиболее обобщенными типами и их аргументом типа. Таким образом следующее не работает:

string myString = new List<string>();
0 голосов
/ 10 января 2019

Когда вы объявляете

Nullable<bool> theNullableBoolThatsFalse = false;

theNullableBoolThatsFalse может содержать 'true', 'false' или 'null' аналогично theNullableBoolThatsNull может содержать логические значения и Null. И когда вы присваиваете значение Null, оно становится пустым и не может быть другого типа. Это не относится ни к какому объекту. Дополнительная информация о Nullable

0 голосов
/ 10 января 2019

Nullable<T> класс имеет неявные и явные реализованные операторы, которые используются в таких случаях "из коробки", взгляните на документацию

Вот выдержка из исходного кода :

[System.Runtime.Versioning.NonVersionable]
public static implicit operator Nullable<T>(T value) {
    return new Nullable<T>(value);
}

[System.Runtime.Versioning.NonVersionable]
public static explicit operator T(Nullable<T> value) {
    return value.Value;
}
...