I came across this and am curious as to why is it not possible to use the is
operator to discern between bool
and Nullable<bool>
? Example;
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);
}
Calling Main()
gives;
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!
I'd expect;
Considering theBool:
It's a bool!
Considering theNullableBoolThatsFalse:
It's a Nullable<bool>!
Considering theNullableBoolThatsNull:
It's a null!
Why do both bool
and Nullable<bool>
match each other?
What have I tried;
- I've consulted the docs for
Nullable
,is
,switch
and pattern matching. - I think this might be related to unboxing of the value when it's passed into the method?
I think it might be unique to Nullable
because I don't run into the same problems for other generic types. For example;
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);
}
Gives;
Considering theBool:
It's a bool!
Considering theListOfBool:
It's a List<bool>
I'm not looking to solve a problem. Just interested as to why it works this way.
Answers so far suggest it's implicit
and explicit
conversions that cause this behaviour but I haven't been able to replicate with the following example;
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);
}
Gives;
Considering a;
It's a A!
Considering b;
It's a B!
The docs for is
say:
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.
Are reference conversions, boxing conversions, and unboxing conversions something the framework decides?
false
can safely be converted (via an implicit cast) to bothbool
andbool?
.null
on the other hand can only be converted toboo?
, which is whyvalue is bool
returns false. – NickolaiNullable
also points at this in the declaration ofGetUnderlyingType
. If you make your comment an answer I'll accept it. – Impose