If System.Double initializes the constant field "NaN" to double.NaN, does it mean that "Double" and "double" don't refer to the same type?

StackOverflow https://stackoverflow.com/questions/21779725

  •  11-10-2022
  •  | 
  •  

Question

In the Visual Studio C# editor, when you right-click on the keyword "double" or the identifier "Double" and select "Go to Definition", VS will tell you that "NaN" is declared and initialized as follows:

 public const double NaN = 0.0 / 0.0;
 //public const double NegativeInfinity = -1.0 / 0.0;
 //public const double PositiveInfinity = 1.0 / 0.0;

However, the source code for System.Double, as disassembled from mscorlib.dll, tells a different story:

public const double NaN = double.NaN;
//public const double NegativeInfinity = double.NegativeInfinity;
//public const double PositiveInfinity = double.PositiveInfinity;

Of course, .NET won't let you initialize a const field with a reference to itself (circular definition) or an instance field with a reference to itself (instance fields are not allowed to be initialized with references to instance fields), and while it's possible to initialize a static field with a reference to itself, that's obviously not the case here.

Now, one of the first things most .NET books teach is that the identifier "Double" and the keyword "double" both refer to the same type (the same for int and Int32, string and String, etc), and a quick look at the IL should be enough proof for anyone who doubts this.

With that in mind, how come the System.Double type can get away with a constant field of type double being initialized to itself? Does it mean that,

...the rules that apply to code written by mere .NET programmers don't apply to internal .NET code? ...the disassembler got it wrong? ..."Double" and "double" are not really the same type?

Was it helpful?

Solution

double is a C# keyword - an alias for the type System.Double. Your assumption is that mscorlib.dll was written in C#. Also, the decompiler is wrong. The constant for NaN is there as a definite double value - the decompiler just expects that you want to see that constant as double.NaN rather than the value of double.NaN (which you probably don't even know).

In the compiled IL (inside the dll), constants are not referenced by name. This is a very important thing. If you do eg.:

const int Zero = 0;

Console.Write(Zero);

The code (not the declaration) will compile the same as

Console.Write(0);

This is a very important point to understand, because constant values are resolved at compile-time, not run-time. If you take the constant from a referenced library, and the value of the constant changes in that referenced library, you'll be left with the old value in your code, until you recompile against the new verison.

If you need a "constant" that can change between library versions, you'd better use static properties instead, eg.:

static int Zero { get { return 0; } }

Then what's referenced is actually the property, not its value.

The same behaviour exists with default parameters in C#. They are also resolved at compile time, so they didn't quite make constructor/method overloads obsolete :)

Imagine a method:

public void DoSomething(bool isSafe = true);

All nice and good. But then you decide that the default behaviour should be unsafe, rather than safe. Or worse, you change the meaning of the boolean (this is a significant problem when you're using parameter names like flag :) ). So you change the definition

public void DoSomething(bool isSafe = false);

Everyone who calls your method as DoSomething() will call it with the old value of the parameter, true. Until they recompile. This can be very frustrating and hard to debug if you don't understand when are default parameters resolved :)

Back to the alias keywords - there are situations, where the two aren't the same. However, this is because of the way the compiler parses certain expressions. The prime example is the enum keyword:

enum MyEnum : byte {} // Valid, an enum with an underlying type of byte.

enum MyEnum2 : System.Byte {} // Invalid, unexpected token. 
// "Type byte, sbyte, short, ushort, int, uint, long, or ulong expected"

And since we're already talking about enums, yes, those have the same problem. They are represented by their underlying "number". If you change that, the old number suddenly points to a different "enum value" and vice versa - again, a big problem if your creating or consuming some API :)

OTHER TIPS

Yes they are the same type. The lowercase double is a compiler convenience. The definition of NaN is a special value that your decompiler is hiding from you.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top