null is not false

null is not false

Rate This
  • Comments 38

The way you typically represent a "missing" or "invalid" value in C# is to use the "null" value of the type. Every reference type has a "null" value; that is, the reference that does not actually refer to anything. And every "normal" value type has a corresponding "nullable" value type which has a null value.

The way these concepts are implemented is completely different. A reference is typically implemented behind the scenes as a 32 or 64 bit number. As we've discussed previously in this space, that number should logically be treated as an "opaque" handle that only the garbage collector knows about, but in practice that number is the offset into the virtual memory space of the process that the referred-to object lives at, inside the managed heap. The number zero is reserved as the representation of null because the operating system reserves the first few pages of virtual memory as invalid, always. There is no chance that by some accident, the zero address is going to be a valid address in the heap.

By contrast, a nullable value type is simply an instance of the value type plus a Boolean that indicates whether the value is to be treated as a value, or as null. It's just a syntactic sugar for passing around a flag. This is because value types need not have any "special" value that has no other meaning; a byte has 256 possible values and every one of them is valid, so a nullable byte has to have some additional storage.

Some languages allow null values of value types or reference types, or both, to be implicitly treated as Booleans. In C, you can say:

int* x = whatever();
if (x) ...

and that is treated as if you'd said "if (x != null)". And similarly for nullable value types; in some languages a null value type is implicitly treated as "false".

The designers of C# considered those features and rejected them. First, because treating references or nullable value types as Booleans is a confusing idiom and a potential rich source of bugs. And second, because semantically it seems presumptuous to automatically translate null -- which should mean "this value is missing" or "this value is unknown" -- to "this value is logically false".

In particular, we want to treat nullable bools as having three states: true, false and null, and not as having three states: true, false and different-kind-of-false. Treating null nullable Booleans as false leads to a number of oddities. Suppose we did, and suppose x is a nullable bool that is equal to null:

if (x)
  Foo();
if (!x)
  Bar();

Neither Foo nor Bar is executed because "not null" is of course also null. (The answer to "what is the opposite of this unknown value?" is "an unknown value".) Does it not seem strange that x and !x are both treated as false? Similarly, if (x | !x) would also be treated as false, which also seems bizarre.

The solution to the problem of these oddities is to avoid the problem in the first place, and not make nulls behave as though they were false.

Next time we'll look at a different aspect of truth-determining: just what is up with those "true" and "false" user-defined operators?

  • Slightly off-topic, but still relevant: I'd like to a see a NotNullable<> keyword (perhaps with a ! suffix shorthand?)

    You would apply this to references to indicate that they can *never* be null.

    You can never assign null to a NotNullable<>, and you can only assign a nullable value by using a cast (which would throw if the source value was null).

    This would save a lot of null checking, and reduce the scope for any desire to treat null as false in the first place.

    It might need a bit of route analysis to cover some cases - but no more than you already do for other things, I think.

  • Phil Nash: What would you get when you create a "new NotNullable<string>[100]"?

    It seems hard to imagine how you wouldn't end up with 100 null NotNullable<string> objects, which sort of defeats the purpose of having a NotNullable type to begin with.

  • By contrast, Python makes even more things implicitly convertible to bool: None, empty sequences ((), [], '', {}), 0, 0.0, and False are all False (and possibly any class that implements __nonzero__ or __len__); everything else is True. Crazy useful, but I still had to lookup whether 0.0 was True or False just now.

  • @AG: C# is not C/++. Read my post (on page 1) for more details.

  • @Gabe: you can't create that if you don't assign values to it immediately, just as you can't create an int without a value. If you don't want that restriction, you can default strings to the empty string, for example.

  • @Julian I agree, I also had to read the paragraph several times to understand what Eric was going for - presumably because of my knowledge with python (well and scheme to some lesser degree).

    Personally treating empty collections and null as implicitly null is generally the behavior one would expect and makes for nicer code. I don't like it for integers though - imho `if x % 10 == 0` is clearer than `if not (x % 10)` (also I've no idea whether I actually need the parens there)

    @Gabe Well C++ solves that problem by demanding a default constructor.

  • @Mike Caron: My point was that since it could have been implemented in c/c++ without contradictions (like x | !x evaluating to false) it could as well be implemented in c#. I have the feeling that it was the suggestion of the whole post (by E Lippert).

  • @AG:i think the point is that NULL and false become ambiguous. In C++ you have two states True and False (which could be NULL). Eric states 'In particular, we want to treat nullable bools as having three states: true, false and null'  and in C++ theres only two states for a bool. What erics last example is outlining are the disatvantages a of a full tristate bool system where any expresion involving null is always false. (Which I belive is similar (same?) to sql , which, as a beginer in sql, i sometimes forget and get burned by it).

  • Were nullable types really already on the drawing board when it was decided that only explicit boolean types could be used as conditional expressions? That looks a bit much like a justification after the fact.

    Aside from that, i really cannot see how "m=getmachine(); if(!m) explode(); " could be seen as confusing.

    And to assuage the confusion engendered by tri-state-bools and the potential for screw-ups, there's a different minimal consistent and imo natural way: All nullables but bool convert to true iff not null, nullable<bool> converts from (false, true,null) to (false,true,throw), maybe even with compiler-warnings for some cases.

    And please, don't try to disprove that by using bad definitions for surrounding operators/conventions/the like, especially such that they don't mash with all the rest.

  • if (!(!x)) makes for some fun double negatives... if x was null, would !(!x) be null or false? how would you tell that this is any different to !!x

  • When I was young, clever, and stupid I really liked the fancy things you could do with C/++'s 0/boolean convertability e.g.  while (x--)...

    Now I hate clever people who make unmaintainable debugging nightmares like that, and am glad C# requires explicit declaration of intent (non-anonymous class use of var, and optional parameters have left a bad taste in my mouth).  No more stupid mistakes like if(x+5 = 2) and switch/case fallthroughs.  No power is lost (goto case xyz) but so many unnecessary problems are prevented.  

  • @Ben Voight - but when Nullable was added, a specific decision to support if(p) for bool? p could have been made, as was done for Visual Basic. _That_ is what he is explaining why was not done, rather than the general T? p case (which is rejected for the same reason as the general T p case)

    @Mike Caron - How about having T? be "Nullable<T> if T is a non-Nullable`1 value type, otherwise T". And suitable CLR magic for generics.

  • @Mike Caron

    "int?? myInt = null;

    Console.WriteLine(myInt == null); //???"

    SomeStruct? maps to Nullable<SomeStruct>, which is also a struct.  Therefore, logically, int?? would map to Nullable<Nullable<SomeStruct>>.  Nullable<T> has an implicit conversion from null.  In this conversion it creates a nullable object and set's HasValue to null and leaves the Value as the default.  This means that myInt will be an instance of Nullable with HasValue false.  Value will be a default nullable, and the default is for hasValue to be false and for Value to be the default.  Value is an int, so it will be 0.

    Comparing it to null is essentially the same as myInt.HasValue.  That will be false, so that's what it would output.

    That said, the whole point is moot because the C# lexer doesn't consider ?? a valid suffix to a variable name declaration.

    Nullable<Nullable<int>> (explicity written out) also says that Nullable<int> isn't non-nullable, even though it's a struct.  I would assume that this specially case is explicitly checked for.

    _________________________________________

    Anyways...it seems that the underlying issue here is not so much treating null as false so much as an issue of treating ints as bools, in my opinion.  A Pointer is just an int (in all practical implementations) and a null pointer just happens to be 0 (in all practical implementations).  If an int is convertible to a boolean and a pointer is convert able to int then null is convertible to false through int, not through some magic null-to-boolean conversion.  This frame of mind also removes the inconsistencies with things like if(!null).  null is 0, and NOT 0 is 1, so if(!null) is true.

    Now having said that, I don't think that ints should be treated as bools like they are in C/C++.  Anytime programmers rely on this fact it almost always leads to confusing code, and confusing code leads to bugs, all because you're too lazy to add an ==0 onto the end of your if statement (which of course converts any int into a boolean using the appropriate logic).

  • @servy42:

    There are actually reasonable implementations of C and C++ where a null-pointer is not all bits zero and even some where not all pointers are of equal size.

    And converting to boolean is consistently a literal !=0 check, which due to how nullpointer-constants are written compares with a nullpointer-constant if you feed in a pointer, no integers in sight, really.

    I don't think there is a way in C# without unsafe code to find out about the implementation of nullpointers, nor is there in the standard any like guarantee. Might be wrong there.

    BTW: As long as there are no nullables, there's no issue. Thus no problem for C/C++/thelikes. And about converting from pointer to int: In unmanaged code/languages you can _force_ the compiler to do whatever you say, so what? That's your lookout as programmer.

  • Chris B. wrote:

       var numbers = new int[0];

       bool all = numbers.All(i => false);

    This is not confusing at all. This is logically the same as:

       var numbers = new int[0];

       bool all = !numbers.Any(i => true);

    Are there any numbers that match this predicate? No, because there are no numbers at all. So “Any()” must return false, and thus, “All()” must return true, otherwise empty collections would present a confusing (and contradictory) special case.

Page 2 of 3 (38 items) 123