61

Dear all, I wonder what is the type of null literal in C#?

In Java, the null literal is of the special null type:

There is also a special null type, the type of the expression null, which has no name. Because the null type has no name, it is impossible to declare a variable of the null type or to cast to the null type. The null reference is the only possible value of an expression of null type. The null reference can always be cast to any reference type.

In C++11, there is nullptr (the recommended version of the old buddy NULL), which is of type std::nullptr_t.

I searched MSDN about C#, but the specification doesn't seem to say anything about that.

Vlad
  • 35,022
  • 6
  • 77
  • 199

3 Answers3

58

According to the ECMA C# language specification:

9.4.4.6 The null literal:

The type of a null-literal is the null type (§11.2.7).

11.2.7 The null type:

The null literal (§9.4.4.6) evaluates to the null value, which is used to denote a reference not pointing at any object or array, or the absence of a value. The null type has a single value, which is the null value. Hence an expression whose type is the null type can evaluate only to the null value. There is no way to explicitly write the null type and, therefore, no way to use it in a declared type. Moreover, the null type can never be the type inferred for a type parameter (§25.6.4)

So to answer your question, null is it's own type - the null type.

Although it's odd how it's not mentioned in the C# 4.0 language specification or the C# 3.0 language specification but is mentioned in the overview of C# 3.0, the ECMA C# language specification and the C# 2.0 language specification.

Outfrost
  • 202
  • 2
  • 7
Johannes Kommer
  • 6,401
  • 1
  • 39
  • 45
  • Strange enough, your reference to ECMA standard points at the document which is not the same as the one at MSDN: http://msdn.microsoft.com/en-us/library/ms228593.aspx – Vlad Nov 20 '11 at 20:55
  • @Vlad that's quite interesting, it might be worth seeing what [Eric Lippert](http://blogs.msdn.com/b/ericlippert/) has to say about it. – Johannes Kommer Nov 20 '11 at 21:00
  • I think, the reasoning for having a separate type for `null` is the same as for Java/C++. I wonder however why this didn't find its way into MSDN. – Vlad Nov 20 '11 at 21:03
  • @Vlad perhaps it's in a seperate document on MSDN? (CLR / CTS) whereas ECMA wanted it added to the base document? – Johannes Kommer Nov 20 '11 at 21:04
  • Well, `null` literal itself is perhaps a language-specific entity. But CLI knows about nulls and types, so it has to be in the CLR specs, too. – Vlad Nov 20 '11 at 21:09
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/5178/discussion-between-j-kommer-and-vlad) – Johannes Kommer Nov 20 '11 at 21:11
  • @Vlad I saw, quite interesting to be honest! Explains why references to the null-type started to disappear in the later specs we found. – Johannes Kommer Nov 21 '11 at 18:23
52

UPDATE: This question was the subject of my blog in July 2013. Thanks for the great question!


J.Kommer's answer is correct (and good on them for doing what was evidently a lot of spec digging!) but I thought I'd add a little historical perspective.

When Mads and I were sorting out the exact wording of various parts of the specification for C# 3.0 we realized that the "null type" was bizarre. It is a "type" with only one value. It is a "type" that Reflection knows nothing about. It is a "type" that doesn't have a name, that GetType never returns, that you can't specify as the type of a local variable or field or anything. In short, it really is a "type" that is there only to make the type system "complete", so that every compile-time expression has a type.

Except that C# already had expressions that had no type: method groups in C# 1.0, anonymous methods in C# 2.0 and lambdas in C# 3.0 all have no type. If all those things can have no type, we realized that "null" need not have a type either. Therefore we removed references to the useless "null type" in C# 3.0.

As an implementation detail, the Microsoft implementations of C# 1.0 through 5.0 all do have an internal object to represent the "null type". They also have objects to represent the non-existing types of lambdas, anonymous methods and method groups. This implementation choice has a number of pros and cons. On the pro side, the compiler can ask for the type of any expression and get an answer. On the con side, it means that sometimes bugs in the type analysis that really ought to have crashed the compiler instead cause semantic changes in programs. My favourite example of that is that it is possible in C# 2.0 to use the illegal expression "null ?? null"; due to a bug the compiler fails to flag it as an erroneous usage of the ?? operator, and goes on to infer that the type of this expression is "the null type", even though that is not a null literal. That then goes on to cause many other downstream bugs as the type analyzer tries to make sense of the type.

In Roslyn we will probably not use this strategy; rather, we'll simply bake into the compiler implementation that some expressions have no type.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 2
    I was hoping Roslyn would enable asking what the type of any expression is, and would add type definitions for all those non-type types. How else would you answer the question "what type is the expression `null`?" – configurator Nov 21 '11 at 17:16
  • @Eric: but what is the official Microsoft's position than? Does `null` expression have a type at all? (It could be not so surprising if it doesn't, taking into account that lambdas etc. don't have a type.) – Vlad Nov 21 '11 at 18:29
  • 6
    @Vlad: The official position as of C# 3.0 is that "null" does not have a type. – Eric Lippert Nov 21 '11 at 18:35
  • 2
    @Eric `null ?? null` compiles just fine in `C# 4.0` – bevacqua May 06 '12 at 00:33
  • 1
    If I write string b = null; the IL opcode is something like this IL_0025: ldnull where into spec is said that "LdNull Pushes a null reference (type O) onto the evaluation stack". So what's type O? is it the internal implementation of a null literal? – xdevel2000 Nov 24 '15 at 10:01
  • @xdevel2000: The CLI specification uses the notation `O` to mean a managed object reference, `&` to mean a managed pointer to a variable and `*` to mean an unmanaged pointer to a variable. – Eric Lippert Nov 24 '15 at 14:42
  • So, can we say that this O is the "real implemented" type of a null? Can we say that it represents what you said? """As an implementation detail, the Microsoft implementations of C# 1.0 through 5.0 all do have an internal object to represent the "null type".""" – xdevel2000 Nov 24 '15 at 15:01
  • @xdevel2000: You could, but how would it profit you to do so? Begin by carefully examining what it even means to say that an expression "has a type". Does it mean that certain methods will be available at runtime? Because they won't be available on a null reference. Does it mean that if the expression had GetType called on it, a particular Type object would be created? That's not true of the null reference. We invent type systems because they give us some powers of reasoning; what additional power does your proposal grant? – Eric Lippert Nov 24 '15 at 15:07
  • Hi @EricLippert, when I say `Cat cat = null; Apple apple = null`, it indicates `null` is assignable to both `Cat` and `Apple`. In OOP the right hand side needs to be derived from left hand side for that to work. But how could `null` be both derived from `Cat` and `Apple`? What should I make out of it? – KFL Oct 18 '16 at 22:58
  • 3
    @KFL: Since you've deduced a falsehood from a sensible argument, one of your premises must be incorrect. Your belief that in OOP languages, assignability of an expression x to a variable of t type T implies (1) that x has a type T' and (2) that T' is a subtype of T, is simply false. You could find many examples of this statement being falsified, starting with `double y = 2 + 2;` The expression is of type int, the variable is of type double, and there is no subtyping relationship between int and double. – Eric Lippert Oct 19 '16 at 00:05
  • 2
    @KFL: In C# `null` has no type at all; it certainly is not a subtype of anything. Again, the idea that assignment compatibility implies subtyping is false; it's the other way. Subtyping implies assignment compatibility. – Eric Lippert Oct 19 '16 at 00:06
  • 2
    @KFL: Also, you have a strange idea that OOP implies a particular kind of type system. OOP is about organizing code into objects that encapsulate functionality and send messages to each other. That some famous OOP languages have nominal subtyping does not mean that all OOP languages are required to have nominal subtyping. – Eric Lippert Oct 19 '16 at 00:08
  • 2
    This might be a bit late, but as a functional programmer I'd really like to be able to use the null type as a generic type, like Unit or () in functional languages. Or have null be of type void and just let us use void as a generic parameter. – Dracam Jul 03 '20 at 06:54
  • 2
    @Dracam: Me too. There are at least three kinds of "void" -- "I return something that can always be discarded" -- unit -- "I return nothing at all" -- void -- and "I don't even return" -- never -- but the type system only represents "return nothing", and you can't use it as a generic type argument. It's unfortunate. – Eric Lippert Jul 06 '20 at 20:32
1

Despite of having no runtime type, null can be cast to a type at compile time, as this example shows.

At runtime, you can find that variable stringAsObject holds a string, not only an object, but you cannot find any type for variables nullString and nullStringAsObject.

public enum Answer { Object, String, Int32, FileInfo };
private Answer GetAnswer(int i) { return Answer.Int32; }
private Answer GetAnswer(string s) { return Answer.String; }
private Answer GetAnswer(object o) { return Answer.Object; }

[TestMethod]
public void MusingAboutNullAtRuntimeVsCompileTime()
{
    string nullString = null;
    object nullStringAsObject = (string)null;
    object stringAsObject = "a string";

    // resolved at runtime
    Expect.Throws(typeof(ArgumentNullException), () => Type.GetTypeHandle(nullString));
    Expect.Throws(typeof(ArgumentNullException), () => Type.GetTypeHandle(nullStringAsObject));
    Assert.AreEqual(typeof(string), Type.GetTypeFromHandle(Type.GetTypeHandle(stringAsObject)));
    Assert.AreEqual(typeof(string), stringAsObject.GetType());

    // resolved at compile time
    Assert.AreEqual(Answer.String, this.GetAnswer(nullString));
    Assert.AreEqual(Answer.Object, this.GetAnswer(nullStringAsObject));
    Assert.AreEqual(Answer.Object, this.GetAnswer(stringAsObject));
    Assert.AreEqual(Answer.Object, this.GetAnswer((object)null));
    Assert.AreEqual(Answer.String, this.GetAnswer((string)null));
    Assert.AreEqual(Answer.String, this.GetAnswer(null));
}

// Uncommenting the following method overload
// makes the last statement in the test case ambiguous to the compiler
// private Answer GetAnswer(FileInfo f) { return Answer.FileInfo; }
emrgee
  • 174
  • 3
  • 8