为什么System.Windows.Point和System.Windows.Vector是可变的?

dev*_*xer 16 .net c# struct mutable immutability

鉴于可变结构通常被认为是邪恶的(例如,为什么可变结构"邪恶"?),是否有可能促使.NET框架的设计者制作System.Windows.PointSystem.Windows.Vector变异的潜在好处?

我想理解这一点,所以我可以决定是否有必要使我自己的类似结构变得可变(如果有的话).做出决定PointVector变异可能只是判断错误,但如果有充分理由(例如,表现好处),我想了解它是什么.

我知道我偶然发现了Vector.Normalize()几次该方法的实现,因为它,惊喜(!),并没有带来新鲜感Vector.它只是改变了当前的向量.

我一直认为它应该像这样工作:

var vector = new Vector(7, 11);
var normalizedVector = vector.Normalize(); // Bzzz! Won't compile
Run Code Online (Sandbox Code Playgroud)

但它实际上是这样的:

var vector = new Vector(7, 11);
vector.Normalize(); // This compiles, but now I've overwritten my original vector
Run Code Online (Sandbox Code Playgroud)

......所以,似乎不变性只是为了避免混淆是一个好主意,但同样,也许在某些情况下值得存在这种混淆.

Jef*_*ado 13

这些类型位于System.Windows命名空间中,通常用于WPF应用程序.应用程序的XAML标记是框架的重要组成部分,因此对于很多事情,它们需要一种使用XAML表达的方式.遗憾的是,没有办法使用WPF XAML调用非参数构造函数(但它可能在松散的XAML中),因此尝试使用适当的参数调用构造函数来初始化它是不可能的.您只能自然地设置对象属性的值,这些属性必须是可变的.

这是坏事吗?对于这些类型,我会说不.它们仅用于保存数据,仅此而已.如果你想获得想要的大小Window,你可以访问它DesiredSize来获得Size代表它想要的大小的对象.你不是要"改变所需的大小"通过改变WidthHeight在属性Size你的对象,你提供了一个新的改变大小Size的对象.我相信这样看是很自然的.

如果这些对象更复杂,操作更复杂或有状态,那么是的,你不希望这些类型既不可变也不结构.然而,因为它们就像它可以得到的那样简单和基本(基本上是POD),结构在这里是合适的.


sup*_*cat 5

这种类型是可变的,因为与某些人可能声称的相反,可变值类型语义是有用的。.net 在一些地方试图假装值类型应该与引用类型具有相同的语义。由于可变值类型语义与可变引用类型语义根本不同,假装它们相同会导致问题。然而,这并不使它们变得“邪恶”——它只是显示了对象模型中的一个缺陷,该模型假设对某物的副本进行操作在语义上等同于对原始内容的操作。如果所讨论的事物是对象引用,则为真;通常是正确的——但也有例外——如果它是一个不可变的结构;如果它是可变结构,则为 false。

具有暴露字段的结构的美妙之处之一是,即使是简单的检查也很容易确定它们的语义。如果有Point[100] PointArray,则有 100 个不同的 实例Point。如果有人说PointArray[4].X = 9;,那将改变一项而不会改变PointArray其他。

假设不是使用 struct Point,而是有一个可变类PointClass

class PointClass {public int X; 公共 int Y;};

有多少 PointClass 实例存储在PointClass[100] PointClassArray?有什么办法可以告诉吗?该语句PointClass[4].X = 9会影响 PointClass[2].X 的值吗?怎么样someOtherObject.somePoint.X

虽然 .net 集合不太适合存储可变结构,但我仍然认为:

字典<字符串,点>;
...
  Point temp = myDict["George"];
  温度.X = 9;
  myDict["George"] = temp;

具有相对清晰的语义,至少在没有线程问题的情况下。虽然我认为不幸的是 .net 集合没有提供一种方法,人们可以简单地说myDict[lookupKey].X = 9;我仍然认为上面的代码非常清楚和不言自明,而无需了解任何关于 Point 的事实,除了它有一个称为 X 的公共整数字段。相比之下,如果有一个Dictionary<PointClass>,则不清楚应该做什么来更改与“乔治”相关联的 X 值。也许PointClass与 George 关联的实例没有在其他任何地方使用,在这种情况下,您可以简单地编写适当的字段。另一方面,也有可能其他人已经抓取了MyDict["George"]用于捕获其中的值,并且不期望PointClass他抓取的对象可能会发生变化。

有些人可能认为“Point”应该是一个不可变的结构体,但是somePoint.X = 5;只要知道它somePoint是一个 type 变量Point,而后者又是一个具有公共 int 字段的结构体,就可以完全确定like 语句的效果X。如果Point是不可变结构,则必须改为使用类似somePoint = new Point(5, somePoint.Y);,除了速度较慢之外,还需要检查结构以确定其所有字段都在构造函数中初始化,其中 X 是第一个,Y 是第二个. 在什么意义上这会比 改进somePoint.X = 5;

顺便说一句,可变结构的最大“陷阱”源于这样一个事实,即系统无法区分改变“this”的结构方法和不改变“this”的结构方法。一个重大的耻辱。首选的解决方法是使用返回从旧结构派生的新结构的函数,或者使用接受“ref”结构参数的静态函数。