const,只读和可变值类型

我正在继续研究C#和语言规范,这里有另外一个我不太明白的行为:

C#语言规范在第10.4节中明确说明了以下内容:

常量声明中指定的类型必须是sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double,decimal,bool,string,枚举类型或引用类型。

它还在第4.1.4节中陈述了以下内容:

通过常量声明,可以声明简单类型的常量(第10.4节)。 不可能有其他结构类型的常量,但类似的效果由静态只读字段提供。

好的,所以通过使用静态只读可以获得类似的效果。 读这个,我去了,并尝试以下代码:

static void Main()
{
    OffsetPoints();
    Console.Write("Hit a key to exit...");
    Console.ReadKey();
}

static Point staticPoint = new Point(0, 0);
static readonly Point staticReadOnlyPoint = new Point(0, 0);

public static void OffsetPoints()
{
    PrintOutPoints();
    staticPoint.Offset(1, 1);
    staticReadOnlyPoint.Offset(1, 1);
    Console.WriteLine("Offsetting...");
    Console.WriteLine();
    PrintOutPoints();
}

static void PrintOutPoints()
{
    Console.WriteLine("Static Point: X={0};Y={1}", staticPoint.X, staticPoint.Y);
    Console.WriteLine("Static readonly Point: X={0};Y={1}", staticReadOnlyPoint.X, staticReadOnlyPoint.Y);
    Console.WriteLine();
}

这段代码的输出是:

静态点:X = 0; Y = 0

静态只读点:X = 0; Y = 0

抵销...

静态点:X = 1; Y = 1

静态只读点:X = 0; Y = 0

按一下键退出...

我真的希望编译器给我一些关于变异静态只读字段或失败的警告,以便像引用类型那样对字段进行变异。

我知道可变值类型是邪恶的(为什么微软曾经将Point实现为mutable是一个谜),但是编译器不应该以某种方式警告你试图改变静态只读值类型吗? 或者至少警告你,你的Offset()方法不会有“期望的”副作用?


Eric Lippert解释了这里发生的事情:

...如果该字段是只读的,并且引用发生在声明该字段的类的实例构造函数之外,那么结果是一个值,即由E引用的对象中的字段I的值。

这里重要的一点是结果是字段的值,而不是与字段相关的变量。 只读字段不是构造函数之外的变量。 (这里的初始化程序被认为是在构造函数内部;请参阅我之前关于该主题的文章。)

哦,只是要强调可变结构的邪恶,这里是他的结论:

这是可变值类型是邪恶的另一个原因。 尽量使价值类型不变。


只读的一点是,您不能重新分配参考或值。

换句话说,如果你试图这样做

staticReadOnlyPoint = new Point(1, 1);

你会得到一个编译器错误,因为你试图重新分配staticReadOnlyPoint 。 编译器会阻止你这样做。

然而, readonly不强制值或引用对象本身是否可变 - 这是由创建它的人员在类或结构中设计的行为。

[ 编辑 :妥善解决被描述的奇怪行为]

您看到staticReadOnlyPoint看起来是不可变的行为的原因不是因为它本身是不可变的,而是因为它是只读结构。 这意味着每次访问它时,都会收到完整的副本。

所以你的线

staticReadOnlyPoint.Offset(1, 1);

正在访问和变异字段的副本,而不是字段中的实际值。 当你随后写出你的值,然后写出原件的另一个副本(而不是变异的副本)。

您调用Offset所做的副本将被丢弃,因为它永远不会分配给任何东西。


编译器根本没有足够的关于方法的信息来知道该方法会改变结构。 一个方法可能有一个有用的副作用,但不会改变结构的任何成员。 如果技术上可以将这种分析添加到编译器中。 但是这对于居住在另一个装配中的任何类型不起作用。

缺少的成分是一个元数据标记,指示方法不会改变任何成员。 像C ++中的const关键字一样。 无法使用。 如果它被添加到原始设计中,它将会非常不符合CLS标准。 有很少的语言支持这个概念。 我只能想到C ++,但我并没有太多。

Fwiw,编译器确实生成了明确的代码,以确保语句不会意外地修改只读。 这个说法

staticReadOnlyPoint.Offset(1, 1);

被翻译成

Point temp = staticReadOnlyPoint;   // makes a copy
temp.Offset(1, 1);

添加代码然后比较该值并生成运行时错误也仅在技术上可行。 它成本太高。

链接地址: http://www.djcxy.com/p/21077.html

上一篇: const, readonly and mutable value types

下一篇: Why do I NOT get warnings about uninitialized readonly fields?