Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如果System.Double将常量字段“NaN”初始化为Double.NaN,是否意味着“Double”和“Double”不是指同一类型?_C#_.net_Il_Disassembly - Fatal编程技术网

C# 如果System.Double将常量字段“NaN”初始化为Double.NaN,是否意味着“Double”和“Double”不是指同一类型?

C# 如果System.Double将常量字段“NaN”初始化为Double.NaN,是否意味着“Double”和“Double”不是指同一类型?,c#,.net,il,disassembly,C#,.net,Il,Disassembly,在Visual Studio C编辑器中,当右键单击关键字double或标识符double并选择Go to Definition时,VS将告诉您NaN的声明和初始化如下: public const double NaN = 0.0 / 0.0; //public const double NegativeInfinity = -1.0 / 0.0; //public const double PositiveInfinity = 1.0 / 0.0; 但是,System.Double的源

在Visual Studio C编辑器中,当右键单击关键字double或标识符double并选择Go to Definition时,VS将告诉您NaN的声明和初始化如下:

 public const double NaN = 0.0 / 0.0;
 //public const double NegativeInfinity = -1.0 / 0.0;
 //public const double PositiveInfinity = 1.0 / 0.0;
但是,System.Double的源代码(从mscorlib.dll反汇编而来)讲述了一个不同的故事:

public const double NaN = double.NaN;
//public const double NegativeInfinity = double.NegativeInfinity;
//public const double PositiveInfinity = double.PositiveInfinity;
当然,.NET不允许您使用引用自身的循环定义初始化常量字段,也不允许使用引用自身的实例字段初始化实例字段。虽然可以使用引用自身的实例字段初始化静态字段,这里显然不是这样

现在,大多数.NET书籍教给我们的第一件事是,标识符Double和关键字Double都指相同的类型,int和Int32、string和string等的类型相同,快速查看IL应该足以证明任何怀疑这一点的人

考虑到这一点,System.Double类型怎么能在Double类型的常量字段被初始化为自身的情况下逃脱呢?这是否意味着

…适用于纯.NET程序员编写的代码的规则不适用于内部.NET代码? …反汇编程序搞错了?
…Double和Double实际上不是同一种类型?

是的,它们是同一种类型。小写的double是一种方便的编译器。NaN的定义是反编译器对您隐藏的一个特殊值。

double是一个C关键字,是System.double类型的别名。您的假设是mscorlib.dll是用C编写的。而且,反编译器是错误的。NaN的常量是作为一个确定的双精度值存在的-反编译器只是希望您将该常量视为double.NaN,而不是double.NaN的值,您可能甚至不知道该值

在dll内编译的IL中,常量不按名称引用。这是一件非常重要的事情。如果你这样做,例如:

const int Zero = 0;

Console.Write(Zero);
声明以外的代码将编译为

Console.Write(0);
这一点非常重要,因为常量值是在编译时解析的,而不是在运行时解析的。如果从引用的库中获取常量,并且常量的值在该引用的库中发生了更改,那么代码中将保留旧值,直到针对新的verison重新编译为止

如果您需要一个可以在库版本之间更改的常量,最好使用静态属性,例如:

static int Zero { get { return 0; } }
那么所引用的实际上是属性,而不是它的值

C中的默认参数也存在同样的行为。它们在编译时也会被解析,因此它们不会使构造函数/方法重载过时:

想象一种方法:

public void DoSomething(bool isSafe = true);
一切都很好。但随后您决定默认行为应该是不安全的,而不是安全的。更糟糕的是,您更改了布尔值的含义。当您使用参数名称(如标志)时,这是一个严重的问题。所以你改变了定义

public void DoSomething(bool isSafe = false);
将您的方法称为DoSomething的每个人都将使用参数的旧值true来调用它。直到它们重新编译。如果您不了解默认参数是何时解析的,那么这可能会非常令人沮丧,并且很难调试:

回到别名关键字-有些情况下,两者不一样。然而,这是因为编译器解析某些表达式的方式。主要示例是enum关键字:

既然我们已经在讨论枚举,是的,它们也有同样的问题。它们由它们的基本数字表示。如果更改该值,旧的数字会突然指向不同的枚举值,反之亦然-如果您创建或使用某些API,这也是一个大问题:

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"