为什么不能使用 is 运算符来区分 bool 和 Nullable<bool>?

Why is it not possible to use the is operator to discern between bool and Nullable<bool>?

我遇到了这个问题,很好奇为什么不能使用 is 运算符来区分 boolNullable<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!

为什么 boolNullable<bool> 都匹配?

我尝试了什么;

我认为它可能是 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>

我不是要解决问题。只是对为什么它以这种方式工作感兴趣。

到目前为止的答案表明是 implicitexplicit 转换导致了这种行为,但我无法通过以下示例进行复制;

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 的文档说:

It only considers reference conversions, boxing conversions, and unboxing conversions; it does not consider user-defined conversions or conversions defined by a type's implicit and explicit operators. The following example generates warnings because the result of the conversion is known at compile-time. Note that the is expression for conversions from int to long and double return false, since these conversions are handled by the implicit operator.

引用转换、装箱转换和拆箱转换是由框架决定的吗?

Nullable<T> class 有 implicit and explicit operators implemented which are used in such cases out of the box, take a look at documentation

以下是 source code 的摘录:

[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;
}

false 可以安全地转换为 boolbool?,因为它们之间有一个隐式转换运算符。

另一方面,

null 无法转换为 bool,这就是为什么 null is bool returns false.

is 运算符不(也不能)关心您如何声明 变量——如果有的话。它只是指示在运行时 提供的值的类型。你也可以这样写:

WhatIsIt(false)

您希望该方法在这里表现如何?它只是尝试将值转换为两种类型 - 它可以 - 因此 returns 两者都为真。

为什么它对其他泛型不起作用,原因很简单,因为大多数泛型类型和它们的类型参数之间没有隐式转换。因此以下内容不起作用:

string myString = new List<string>();

申报时

Nullable<bool> theNullableBoolThatsFalse = false;

theNullableBoolThatsFalse 可以保存 'true'、'false' 或 'null' 同样 theNullableBoolThatsNull 可以保存布尔值和 Null。当您分配 Null 时,它完全变成 null can't be any other type 。它不指代任何对象。 Furthe Information About Nullable

boolNullable<bool> 在传递给您的方法时表现相同的原因是,每当您装箱 Nullable<T> 时,它实际上并没有装箱可为 null 的值,而是解包nullable 和 boxes that 的值。如果可为 null 的值为 null,那么您最终只会得到 null,而不是 HasValuefalse 的盒装 Nullable<T>

如果您装箱一个非空值,它只会装箱 Nullable<T>Value。所以从 WhatIsIt 的角度来看,前两个调用 几乎无法区分 ,因为 传入的是完全相同的值

这就留下了为什么 both is 检查 return true 的问题,即使在这两种情况下传入的内容,是一个带框的布尔值,而不是 Nullable<T>。 C# 语言规范第 7.10.10 节回答了这个问题:

If T is a nullable type, the result is true if D is the underlying type of T.

在这种情况下,这是考虑 E is T 并且 D 之前定义为 E 的计算值,其中:

If the type of E is a nullable type, D is the underlying type of that nullable type.

这意味着 is 运算符 具体 定义为将可空类型视为等同于它们的基础类型,无论您如何混合和匹配实际值正在检查的类型以及您正在使用可为 null 的值检查的类型以及可为 null 的基础类型。