Reputation: 211
I was eating lunch with a friend yesterday and they were complaining about null
in C#. He stated that null
was illogical. I decided to test his claims, so I tested some simple logical propositions:
Console.WriteLine(null == null); //True
//Console.WriteLine(null == !!null); //BOOM
Console.WriteLine(10 >= null); //False
Console.WriteLine(10 <= null); //False
Console.WriteLine(!(10 >= null)); //True
Console.WriteLine(!(10 <= null)); //True
Checking equality seems straightforward and this is what I would expect. The greater/less than statements however are logical contradictions which I find really confusing! Shouldn't these throw? The negation operation throws as you would expect.
If I try to run comparisons (aside from equality) using null
in Ruby or Python I get a type error along the lines of "cannot compare a number to nil". Why doesn't C# do this?
Upvotes: 17
Views: 2088
Reputation: 10874
I believe this is for historical reasons. The main motivation for adding nullable value types to C# in version 2.0 of the language was to alleviate the pain of working with nullable sql columns in ADO.NET. Therefore, addition of nullable numbers was designed to work as in sql, and so on.
Upvotes: 1
Reputation: 403
I'm not a C# language designer, so (shrug), but personally, I see it as an original type system failure, so my head can just accept it as-is.
The reason is that logically, you can't compare a deterministic value (eg. int) with an indeterministic value (nullable int), without some conversion either way. They are really 2 different types, but modern programming languages just try and smudge them together in their own way.
I also see the null as a good and useful thing, as we never have complete data and never want complete data as we only need a subset of it for the activity at hand.
Upvotes: 2
Reputation: 12181
Excellent question.
Try not to think of null
as a specific value but rather "nothing to see here." The documentation defines null
as
The
null
keyword is a literal that represents a null reference, one that does not refer to any object.
With that in mind, the fact that null
is not an object means that the classical laws of thought don't exactly apply to it (or, at least, don't apply to it in the same way that it would apply to an actual object).
That being said, the fact that 10 >= null
and 10 <= null
are both false
isn't, strictly speaking, a contradiction - after all, null
is quite literally nothing. If you said 10 >= (some actual thing)
and 10 <= (some actual thing)
were both false, then clearly that would be contradictory, but you can't exactly have a contradiction in the classical sense without some actual object. Aristotle's definition of the law from Metaphysics is as follows:
It is impossible that the same thing can at the same time both belong and not belong to the same object and in the same respect, and all other specifications that might be made, let them be added to meet local objections...
So, we have a bit of a "loophole" here in a sense. At least as Aristotle formulated the Law of Non-Contradiction, it was referring specifically to objects. There are, of course, multiple interpretations of the Law of Non-Contradiction at this point.
Now, turning to the case of 10 + null
. We could say that this is the same thing as null + 10
if it's easier. In a sense, then, what should happen at this point - should null
"swallow up" the 10, or should we just say "10 + (nothing at all) really should just equal 10"? Truthfully, I don't have a very convincing answer here from a logical perspective beyond saying "well, that's kind of a design choice." I suspect that the language designers wanted to distinguish 10 + null
from 10 + 0
, but I don't have documentation to prove that. (Indeed, it would be slightly strange if they were the same; after all, 0 is an actual value that can be constructed from the natural numbers, but null
is "no value whatever").
Upvotes: 14
Reputation: 62248
I know that it's in the language specification somewhere, that's not what I'm interested in knowing. I'm interested in understanding why these logical violations seem to exist, or if my understanding of logical laws is incorrect.
Programming language should not obey philosophy implied logical rules, by any means, as programming language meant to solve concrete industry/business related problems. A lot of the languages do not obey rules even of mathematical thinking, like algebraic expressions, category theory and whatnot, so...
10 must be greater or less than null ... shouldn't it?
No, "The null keyword is a literal that represents a null reference, one that does not refer to any object", and according to C#
rules, compiler scans available operators for a provided type and if one of the participants is null
, outcome of expression will be null
.
Look for lifted operators
Also how can 10 + null be null?
See above.
Upvotes: 10
Reputation: 2243
It helps if you don't think of 'null' as a value - it's a concept of nothingness, or lack-of-a-value. It's not just C# - you'll run into the same thing in SQL (NULL + 'asdf' will result in NULL.)
So stuff like (NULL < 10) itself doesn't make the most logical sense - you're comparing a concept of nothingness to an actual number. Technically, no, nothingness is NOT less than 10. It's not greater than 10, either. That's why it returns back false in both cases.
The reason why you're cautioned away from NULL has nothing to do with logic. Here's a great example of why NULL's in C# will often result in bugs/errors:
private NumericalAnalysis AnalyzeNumber(int someValue)
{
if (someCondition)
return null;
return new NumericalAnalysis(someValue);
}
... okay, so now we can just call AnalyzeNumber() and start working with NumericalAnalysis:
NumericalAnalysis instance = AnalyzeNumber(3);
if (instance.IsPrime)
DoSomething();
... whoops! You just ran into a bug. You called AnalyzeNumber() but didn't check whether the return value was null before using it. After all, AnalyzeNumber() will sometimes return a null value - so when instance.IsPrime is invoked, it'll bomb. You'd literally have to do a "if (myVar == null)" check every single time that function was called - and if you forget, you just introduced a bug.
Upvotes: 4