简体   繁体   中英

In .Net/C#, is null strongly-typed?

Does null have a type? How is a null value represented internally? What is happening in the following code?

void Foo(string bar) {...}
void Foo(object bar) {...}

Foo((string)null);

Edit: The answers so far have been unspecific and too high-level. I understand that a reference type object consists of a pointer on the stack which points to a location on the heap which contains a sync block index, a type handle and the object's fields. When I set an instance of an object to null , where does the pointer on the stack point to, exactly? And in the code snippet, is the cast simply used by the C# compiler to decide which overload to call, and there is not really any casting of null going on?

I am looking for an in-depth answer by someone who understands the CLR internals.

The cast to string in your code sample doesn't give the null a type, as null cannot have a type itself. If you want proof of this, then execute the following code where you can see that null is always equal to itself, irrespective of what the type of variable it was assigned to is:

string s = null;
IPAddress i = null;
Console.WriteLine(object.Equals(s, i)); // prints "True"
Console.WriteLine(object.ReferenceEquals(s, i)); // prints "True"

What the cast does is tell the compiler which overload to choose. As null doesn't have a type it does not know whether to choose the overload that takes an object or a string as the value could be interpreted as either. So you're helping it out by saying "Here's a null value that should be treated as if it was a string".


If you want to see what's going on underneath, then look at the IL from your code. The relevant bit for the method call is something like the following in textual IL (depending on your namespace and class name, etc):

ldnull 
call void ConsoleApplication1.Program::Foo(string)

So all that's happening is that a null is being loaded on the stack, and then this is consumed by the overload that takes the string, as overload resolution is performed at compile time so the method to call is baked into the IL.

If you want to see what ldnull does, and why it's different from just using something like ldc.i4.0 to load a zero onto the stack then see this answer (if you don't want to follow the link, the reason is that it's a size-agnostic zero which otherwise doesn't exist in the CLR).

null does not have type, and "And in the code snippet, is the cast simply used by the C# compiler to decide which overload to call, and there is not really any casting of null going on?" is exactly what is going on. Let's look at the generated IL.

  IL_0001:  ldnull
  IL_0002:  call       void ConsoleApplication1.Program::Foo(string)

Notice the ldnull loading null to the stack. It is simply null, not null as a string or something else. What matters is the second line, where IL is explicitly calling the overload that receives a string. Here is what happens if you call, casting to object:

  IL_0001:  ldnull
  IL_0002:  call       void ConsoleApplication1.Program::Foo(object)

So, yes, the cast is a C# artifact so the compiler knows what overload to call.

It will call Foo(string bar).

You can cast null fine.

When you set a reference object to null , the pointer (behind) points to a special place in memory that is designated null . So when you cast to null , your really creating a pointer of the specific Type that points to null .

As demonstrated by others (1) (2) , this doesn't seem to be the case because the compiler resolves the overloading emitting the IL according to the specific null cast.

I know that reference variables have a type associated with them. I thought that this would be behind this null casting but I was wrong.

The ldnull opcode pushes a null reference (type O) onto the stack. And, even if this null reference has a Type associated with it, the call accepts this reference as a valid argument of string . At least, this is my understanding of it. If someone have any corrections to this please feel free to correct me.

The null keyword is a literal that represents a null reference, one that does not refer to any object. null is the default value of reference-type variables. From MSDN . So the default value of String in your example.

// A null string is not the same as an empty string.
string s = null;
string t = String.Empty; // Logically the same as ""

What you are doing is equivalent to using default as per above:

int equal = string.Compare((string)null, default(string));

NULL is a Marker, it does not have a data type.When you assign .NULL to a field or variable, the value changes to NULL but the data type of the field or variable does not change.

The reason for assigning string type to null in your example I think is in order to show witch method will be used(in this case "void Foo(string bar) {...}" because this is the method that accepts strings as arguments.)

If you would have called Foo((object)null); the other method would have been used.("void Foo(object bar) {...}")

The type of the null value is defined by ECMA-334 as follows:

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.

The null type is the bottom type of the type hierarchy - the opposite of object; the null type can be thought of as being a sub-type of every nullable type, as the null value can be used wherever any nullable expression occurs.

This does create a weakness in the type system, as it means that any operation whose receiver is a nullable type could be null, and the operation may fail at runtime with an exception. In a strong type systems, operations supplied by a type are guaranteed if the the receiver is of that type ( ie you wouldn't get a null pointer exception, which is really saying that the null type doesn't implement whatever method you've invoked on it, although the null value can result from the expression of whatever type you thought you were using ) .

In your code, the methods are overloaded and the static type of the expression yielding the argument is used for overload resolution. Because of the cast to string, the expression has type string ( the null type being a sub-type of string this is an up-cast so safe according to the C# type system ). As a first approximation, the compiler selects the most specific overload visible, so choses the string version of Foo rather than the object version.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM