为什么是System.Windows.Point& System.Windows.Vector可变吗?

时间:2012-01-19 01:47:23

标签: c# .net struct immutability mutable

鉴于可变结构通常被认为是邪恶的(例如,Why are mutable structs “evil”?),是否有潜在的好处可能促使.NET框架的设计者制作System.Windows.Point& System.Windows.Vector可变吗?

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

我知道我曾多次偶然发现Vector.Normalize()方法的实现,因为它惊讶(!),不会返回新的Vector。它只是改变了当前的向量。

我一直认为它应该是这样的:

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

但它实际上是这样的:

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

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

3 个答案:

答案 0 :(得分:13)

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

这是件坏事吗?对于这些类型,我会说不。它们仅用于保存数据,仅此而已。如果您希望获得Window想要的大小,则可以访问DesiredSize以获取表示所需大小的Size对象。您不是要通过更改所获得的Width对象的HeightSize属性来“更改所需的大小”,而是通过提供新的{{1}来更改大小对象。我相信这样看是很自然的。

如果这些对象更复杂,操作更复杂或有状态,那么是的,你不希望这些类型既不可变也不结构。然而,由于它们只是尽可能简单和基本(基本上是一个POD),结构在这里是合适的。

答案 1 :(得分:4)

这些类型是可变的,因为与某些人可能声称的相反,可变值类型语义是有用的。有几个地方.net试图假装值类型应该具有与引用类型相同的语义。由于可变值类型语义与可变引用类型语义根本不同,因此假装它们相同会导致问题。然而,这并没有使它们变得“邪恶” - 它只是在对象模型中显示出一个缺陷,它假设对某些东西的复制在语义上等同于对原作的行为。如果有问题的东西是对象引用,则为True;通常是真的 - 但有例外 - 如果它是一个不可变的结构;如果它是一个可变的结构,则为false。

关于具有暴露场的结构的一个美妙的事情是,即使是简单的检查也可以很容易地确定它们的语义。如果一个人有Point[100] PointArray,则其中一个人有100个不同的Point个实例。如果有人说PointArray[4].X = 9;,则会更改PointArray中的一项,而不会更改其中一项。

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

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

PointClass[100] PointClassArray中存储了多少个PointClass实例?有什么方法可以说出来吗?语句PointClass[4].X = 9是否会影响PointClass [2] .X的值?那么someOtherObject.somePoint.X呢?

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

Dictionary<string, Point>;
...
  Point temp = myDict["George"];
  temp.X = 9;
  myDict["George"] = temp;

具有相对清晰的语义,至少在没有线程问题的情况下。虽然我觉得很遗憾.net集合并没有提供一种方法可以简单地说myDict[lookupKey].X = 9;我仍然认为上面的代码非常明确且不言自明而不必知道任何东西关于Point,除了它有一个名为X的公共整数字段这一事实。相比之下,如果有一个Dictionary<PointClass>,那么就不清楚应该做些什么来改变与“乔治”。也许与George相关联的PointClass实例不会在其他任何地方使用,在这种情况下,可以简单地编写适当的字段。另一方面,其他人也可能抓取MyDict["George"]的副本以捕获其中的值,并且不期望他抓住的PointClass对象可能会改变。

有些人可能会认为“Point”应该是一个不可变的结构,但只知somePoint.X = 5;somePoint类型的变量,就可以完全确定像Point这样的语句的效果。 ,反过来又是一个带有名为X的公共int字段的结构。如果Point是一个不可变的结构,则必须改为说somePoint = new Point(5, somePoint.Y);之类的东西,除了速度较慢之外,还需要检查结构以确定它的所有字段都在构造函数中初始化,X是第一个,Y是第二个。在什么意义上,这会比somePoint.X = 5;改善?

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

答案 2 :(得分:1)

的可能性:

  1. 当时对于那些不考虑人们会咬人的用例似乎是一个好主意。 List<T>.Enumerator是一个可变结构,因为当时利用经常发生的微操作似乎是个好主意。它几乎是可变结构的“海洋孩子”,因为它被咬了不止一些人。不过,当时对某人来说似乎是个好主意......
  2. 他们确实想到了缺点,但他们知道一些用例,其中性能差异在结构上有利(它们并不总是)并且被认为是重要的。
  3. 他们不认为结构是邪恶的。 “邪恶”是一种关于下边打击的观点,而不是一个可证明的事实,即使Eric Lippert和Jon Skeet说出来,也不是每个人都同意某些事情。我个人认为他们不是邪恶的,他们只是被误解了;但话又说回来,邪恶往往比对程序员误解更容易处理,所以实际上更糟糕......;)也许那些参与者不同意。