为什么struct可以改变自己的领域?

考虑Foo结构如下:

struct Foo { public float X; public float Y; public Foo(float x, float y) { this.X = x; this.Y = y; } public void Change(float x) { this.X = x; } } 

我理解在构造函数中修改字段,这对我和我理解结构作为值,类似数字的不可变类型是完全合乎逻辑的。

但是,因为人们做不到:

 Foo bar = new Foo(1, 2); bar.X = 5; 

为什么可以使用:

 Foo bar = new Foo(1, 2); bar.Change(5); 

编辑 :如果结构是可变的,那么为什么它们不能在列表中修改或从属性返回?

无法修改表达式,因为它不是变量

你做了一个关键的错误假设。

.NET结构是可变的。 你绝对可以执行bar.X = 5;

您应该将结构设计为不可变的,但是根据您提供的代码,它们是可变的。

看一下这个问题,了解可变结构可能导致麻烦的地方。 结构的不变性

既然一个人做不到

 Foo bar = new Foo(1, 2); bar.X = 5; 

为什么可以使用:

 Foo bar = new Foo(1, 2); bar.Change(5); 

您的原始问题实际上无法回答,因为它是基于一个完全错误的假设 。 两个代码示例都是完全合法的,因此关于为什么一个是非法的问题是荒谬的。 让我们继续你的后续问题:

如果结构是可变的,那么为什么它们不能在列表中修改或从属性返回?

因为变量是可变的,值是不可变的。

这就是为什么他们被称为“变量”,毕竟, 因为他们可以改变

当你说“bar.X = 5”时,“bar”是一个局部变量 。 变量可以改变。

当你说“bar.Change(5)”时,“bar”是一个局部变量 。 变量可以改变。

当你说“myArray [123] .X = 5”时,“myArray [123]”是一个数组元素 ,数组元素是一个变量 。 变量可以改变。

当你说“myDictionary [123] .X = 5”时,“myDictionary [123]” 不是变量 。 该从字典返回,而不是对存储位置的引用 。 由于这是一个值,而不是一个变量,因此没有任何东西可以改变,所以编译器不允许它改变。

一个微妙的问题是,当您尝试更改字段时 ,接收方必须是变量。 如果它不是变量,那就毫无意义; 你显然是在试图改变一个变量而且没有任何东西可以改变。 当你调用一个方法时,接收者必须是一个变量但是如果你有一个值呢? 该方法可能不会尝试改变任何东西,因此应该允许成功。 如果方法的接收者在结构上调用不是变量,编译器实际上做了什么,然后它创建一个新的临时局部变量并使用该变量调用该方法。 所以,如果你说:“myDictionary [123] .Change(5);” 这跟说的一样

 var temp = myDictionary[123]; temp.Change(5); 

这里“temp”是一个变量,允许变异方法改变临时副本。

那现在清楚了吗? 这里的关键点是变量可以改变

通常,所有C#结构都不是不可变的,甚至是只读的结构。 因此,您根本无法将结构设计为不可变的。

所有结构都是可变的,就像在C ++中一样 🙂

不可变性意味着数据结构在语言级别上是不可变的,而C#则不然。 我将向您展示如何使用合法的C#语法打破不变性规则,请注意NotReallyImmutableFoo.X被声明为只读字段。

干杯;)

 namespace test { public unsafe struct MutableFoo { public int Id; public float X; public MutableFoo(int id, float x) { Id = id; X = x; } public void Change(float x) { unsafe { fixed (MutableFoo* self = &(this)) { MutabilityHelper.Rewrite(self, x); } } } } public struct NotReallyImmutableFoo { public long Id; public readonly float X; public NotReallyImmutableFoo(long id, float x) { Id = id; X = x; } public void Change(float x) { unsafe { fixed (NotReallyImmutableFoo* self = &(this)) { MutabilityHelper.Rewrite(self, x); } } } } // this calls breaks up the immutability rule, because we are modifying structures itself public static class MutabilityHelper { struct MutableFooPrototype { int Id; float X; public void Rewrite(float value) { X = value; } } struct NotReallyImmutableFooPrototype { long Id; float X; public void Rewrite(float value) { X = value; } } public static unsafe void Rewrite(NotReallyImmutableFoo* obj, float value) { NotReallyImmutableFooPrototype* p_obj = (NotReallyImmutableFooPrototype*)(*(&obj)); p_obj->Rewrite(value); } public static unsafe void Rewrite(MutableFoo* obj, float value) { MutableFooPrototype* p_obj = (MutableFooPrototype*)(*(&obj)); p_obj->Rewrite(value); } } class Program { static void Main(string[] args) { MutableFoo foo = new MutableFoo(0, 2); foo.X = 3; // X is writeable foo.Change(5); // write X using pointer prototyping NotReallyImmutableFoo nrifoo = new NotReallyImmutableFoo(0, 2); // error CS0191 //nrifoo.X = 3; // X is not writeable nrifoo.Change(3); // anyway, write X using pointer prototyping } } } 

在最底层找到了我的答案: http : //www.albahari.com/valuevsreftypes.aspx

但是,我仍然不明白为什么编译器在这种情况下允许可变结构。

.net中的结构将分段可变性与浅赋值赋值语义以及通过赋值或引用传递的能力相结合。 然而,.net中没有约定,期望通过引用公开类,也不会有任何.net语言编译器提供这样做的方便方法。 通过识别以下内容,语言可以提供具有某些限制的此类function:

   somePoint.X = 5;

可以写成:

 void SetXToFive(ref指向它){it.X = 5;}
 ...
   SetXToFive(ref somePoint);

允许操作Point的代码(通过将其X字段设置为5)来自可访问它的代码。 如果一个具有Point类型属性的对象然后公开一个接受上述方法的委托的例程,那么想要将该属性的字段X设置为5的代码可以将该例程委托给SetXToFive ,这个例程就是然后可以调用任何存储位置保持Point有问题。

请注意,与简单地公开对要操作的事物的引用相比,这种方法的一个优点是Point的所有者将知道操作它的代码何时完成。 如果没有一些编译器的晚餐,这种方法通常会比利益更令人讨厌,但是通过编译器支持,语义可以比通过任何其他方式更加清晰。